diff --git a/.clang-tidy b/.clang-tidy index 9ac1ace65b..7b39447264 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -36,6 +36,7 @@ Checks: '-*, -readability-identifier-length, -readability-implicit-bool-conversion, -readability-magic-numbers, + -readability-math-missing-parentheses, -readability-static-accessed-through-instance, -readability-uppercase-literal-suffix' CheckOptions: diff --git a/.gdbinit b/.gdbinit index a89464ae86..c17d5fc4fe 100644 --- a/.gdbinit +++ b/.gdbinit @@ -24,6 +24,8 @@ python +import struct + ###### static_vector ######## class StaticVectorPrinter(object): @@ -225,5 +227,44 @@ def make_slotted_vector(val): gdb.pretty_printers.append(make_slotted_vector) +###### Brain Floating Point 16 (bf16_t) ###### + +class BFloat16(object): + def __init__(self, val): + self.__val = val + + def to_string(self): + value_uint16 = self.__val['val'] + value_uint32 = value_uint16.cast(gdb.lookup_type('uint32_t')) << 16 + value_float = struct.unpack('!f', struct.pack('!I', value_uint32))[0] + return value_float + + def display_hint(self): + return None + +def make_bf16_t(val): + s = str(val.type.strip_typedefs()) + if 'srsran::strong_bf16_tag' in s: + return BFloat16(val) + +gdb.pretty_printers.append(make_bf16_t) + +class BFloat16Complex(object): + def __init__(self, val): + self.__val = val + + def to_string(self): + return f'{self.__val["real"]} + {self.__val["imag"]}i' + + def display_hint(self): + return None + +def make_cbf16_t(val): + s = str(val.type.strip_typedefs()) + if s == 'srsran::cbf16_t': + return BFloat16Complex(val) + +gdb.pretty_printers.append(make_cbf16_t) + end diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8b8ac92519..6c423c24a6 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -15,8 +15,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, ubuntu-24.04] - compiler: [gcc, clang] + os: [ubuntu-24.04] + compiler: [gcc] steps: - uses: actions/checkout@v3 - name: Install Dependencies diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 40c928c619..b9fb04e0f7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -67,6 +67,7 @@ variables: SLACK_CHANNEL_FAIL: "#ci_gnb" SLACK_CHANNEL_INFO_MSG: "#ci_gnb_verbose" AUTOREBASER_PRS_IN_QUEUE: 1 + MR_PLUGIN_REF: main ################################################################################ ## CI @@ -75,14 +76,14 @@ job cleaner: extends: .job cleaner timeout: 4h rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ pr reminder: extends: .pr reminder rules: - if: $NOTIFY_SLACK != "true" when: never - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ trigger builder: stage: ci @@ -111,7 +112,7 @@ trigger docker: - docker/**/*.{yml,env,json,toml,py,sh,csv,py,toml} - docker/**/Dockerfile - .gdbinit - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ variables: CI_DESCRIPTION: $CI_DESCRIPTION - if: $CI_DESCRIPTION =~ /Weekly/ @@ -126,7 +127,7 @@ trigger docker: matlab nightly: stage: ci rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ variables: ON_SCHEDULE: "true" CI_DESCRIPTION: Nightly @@ -138,21 +139,6 @@ matlab nightly: project: softwareradiosystems/srsgnb_matlab branch: master -plugin nightly: - stage: ci - rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ - variables: - ON_SCHEDULE: "true" - CI_DESCRIPTION: Nightly - SRSRAN_COMMIT: $CI_COMMIT_SHA - NOTIFY_SLACK: "true" - inherit: - variables: false - trigger: - project: ${PLUGIN_REPO} - branch: main - matlab weekly: stage: ci rules: @@ -201,7 +187,7 @@ full-code-format: - if: $ON_MR changes: - .clang-format - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ .codechecker: image: ${CR_REGISTRY_URI}/srsgnb/codechecker:${DOCKER_BUILDER_VERSION} @@ -237,6 +223,11 @@ full-code-format: -*lib/asn1 -*lib/phy/generic_functions/fftx/lib_fftx_dftbat_srcs -*lib/phy/generic_functions/fftx/lib_fftx_idftbat_srcs" >/tmp/codechecker_skip + - | + if [[ $CI_MERGE_REQUEST_LABELS != *"ci-no-plugin"* && -n $PLUGIN_BRANCH ]]; then + git submodule add https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_SHELL_SSH_HOST}/${PLUGIN_REPO}.git ${PLUGIN_PATH} + git submodule set-branch --branch ${PLUGIN_BRANCH} ${PLUGIN_PATH} + fi - | export CC=/usr/bin/clang export CXX=/usr/bin/clang++ @@ -279,6 +270,7 @@ clang-tidy: when: manual allow_failure: false variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF ANALYZER: clang-tidy ANALYZER_ARGS: --analyzer-config clang-tidy:take-config-from-directory=true --tidy-config .clang-tidy ARTIFACT_EXTRA_PATH: "/index.html" @@ -447,7 +439,7 @@ unit coverage: unit coverage dev: extends: unit coverage rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ variables: coverage_report: full when: always # Even if previous stages/required jobs fail @@ -458,7 +450,7 @@ unit coverage dev: pages: stage: documentation rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: always # Even if previous stages/required jobs fail allow_failure: true image: ${GITLAB_REGISTRY_URI}/${CI_TOOLS_REPO}/doxygen:1.9.8-1.2023.7 @@ -511,7 +503,7 @@ update agpl main dryrun: extends: update private branch stage: .post rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: always variables: PRIVATE_BRANCH: agpl_main @@ -523,7 +515,7 @@ create-tags: extends: .create-tag stage: .post rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 3 hours script: diff --git a/.gitlab/ci-shared/build.yml b/.gitlab/ci-shared/build.yml index cdff42361b..d8ebd804c1 100644 --- a/.gitlab/ci-shared/build.yml +++ b/.gitlab/ci-shared/build.yml @@ -70,10 +70,10 @@ variables: CACHE_COMPRESSION_LEVEL: slowest CACHE_REQUEST_TIMEOUT: 5 # minutes - 10 by default # K8 - KUBERNETES_CPU_REQUEST: 6 - KUBERNETES_CPU_LIMIT: 6 - KUBERNETES_MEMORY_REQUEST: 12Gi - KUBERNETES_MEMORY_LIMIT: 12Gi + KUBERNETES_CPU_REQUEST: ${SRS_CPU_LIMIT} + KUBERNETES_CPU_LIMIT: ${SRS_CPU_LIMIT} + KUBERNETES_MEMORY_REQUEST: ${SRS_MEMORY_LIMIT} + KUBERNETES_MEMORY_LIMIT: ${SRS_MEMORY_LIMIT} KUBERNETES_EPHEMERAL_STORAGE_REQUEST: "40G" KUBERNETES_EPHEMERAL_STORAGE_LIMIT: "40G" tags: diff --git a/.gitlab/ci-shared/e2e.yml b/.gitlab/ci-shared/e2e.yml index b458f3a430..57e53d712e 100644 --- a/.gitlab/ci-shared/e2e.yml +++ b/.gitlab/ci-shared/e2e.yml @@ -69,16 +69,16 @@ variables: TRANSFER_METER_FREQUENCY: 5s ARTIFACT_COMPRESSION_LEVEL: slowest RUNNER_AFTER_SCRIPT_TIMEOUT: 1m - KUBERNETES_CPU_REQUEST: 2 - KUBERNETES_CPU_LIMIT: 2 + KUBERNETES_CPU_REQUEST: 1 + KUBERNETES_CPU_LIMIT: 1 KUBERNETES_MEMORY_REQUEST: 2Gi KUBERNETES_MEMORY_LIMIT: 2Gi - KUBERNETES_EPHEMERAL_STORAGE_REQUEST: "20G" - KUBERNETES_EPHEMERAL_STORAGE_LIMIT: "20G" + KUBERNETES_EPHEMERAL_STORAGE_REQUEST: "10G" + KUBERNETES_EPHEMERAL_STORAGE_LIMIT: "10G" <<: *setup_kube_variables GROUP: zmq tags: - - "${RETINA_TAG}" + - retina-e2e-amd64 artifacts: paths: - ${SRSRANDIR}/tests/e2e/log diff --git a/.gitlab/ci-shared/plugin.yml b/.gitlab/ci-shared/plugin.yml index 682c9dfb29..c40e6c27fa 100644 --- a/.gitlab/ci-shared/plugin.yml +++ b/.gitlab/ci-shared/plugin.yml @@ -21,7 +21,7 @@ variables: ###### # CI # ###### -is using latest: +.is using latest: image: ubuntu:24.04 stage: ci rules: @@ -38,6 +38,9 @@ is using latest: echo "Please update to latest srsRAN commit: $SRSRAN_LATEST_COMMIT" exit 1 fi + +is using latest: + extends: .is using latest allow_failure: true download srsran: diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 2d770281d5..eabda58e1d 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -69,15 +69,19 @@ variables: echo "MAKE_ARGS=${MAKE_ARGS}" echo "UHD_VERSION=${UHD_VERSION}" echo "DPDK_VERSION=${DPDK_VERSION}" + echo "SRS_TARGET=${SRS_TARGET}" + echo "PLUGIN_BRANCH=${PLUGIN_BRANCH}" build_srsgnb() { + target="$1" + start_time=$(date +%s) if [ -e "/usr/local/bin/ram_reporter.py" ]; then /usr/local/builder_tools/bin/python3 /usr/local/bin/ram_reporter.py ${CI_PROJECT_DIR}/ram_usage.txt & ram_reporter_pid=$! fi - build_srsran "" ${CMAKE_FLAGS_CMD} + build_srsran "${target}" ${CMAKE_FLAGS_CMD} if [[ -n "$OUTPUT_FINGERPRINT" ]]; then echo "Storing fingerprints of all executables to $OUTPUT_FINGERPRINT" cd build @@ -159,6 +163,8 @@ variables: --exclude=${CI_PROJECT_DIR}/lib/asn1/ngap/.* \ --exclude=${CI_PROJECT_DIR}/lib/asn1/nrppa/.* \ --exclude=${CI_PROJECT_DIR}/lib/asn1/rrc_nr/.* \ + --exclude=${CI_PROJECT_DIR}/plugins/.*/include/srsran/asn1/nrppa/.* \ + --exclude=${CI_PROJECT_DIR}/plugins/.*/lib/asn1/nrppa/.* \ --exclude-lines-by-pattern \".*srsran_assert.*|.*srsran_sanity_check.*\" \ --root=${CI_PROJECT_DIR}" @@ -185,7 +191,11 @@ variables: exit $ret } - - build_srsgnb + if [[ $CI_MERGE_REQUEST_LABELS != *"ci-no-plugin"* && -n $PLUGIN_BRANCH ]]; then + git submodule add https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_SHELL_SSH_HOST}/${PLUGIN_REPO}.git ${PLUGIN_PATH} >/dev/null 2>&1 + git submodule set-branch --branch ${PLUGIN_BRANCH} ${PLUGIN_PATH} >/dev/null 2>&1 + fi + - build_srsgnb "${SRS_TARGET}" - launch_tests timeout: 6h @@ -193,9 +203,9 @@ variables: after_script: &build_after_script - mv ${CI_PROJECT_DIR}/build/coverage.xml ${CI_PROJECT_DIR}/${CI_JOB_ID}_coverage.xml || true - | - mv ${CI_PROJECT_DIR}/build/apps/gnb/gnb /tmp/gnb - mv ${CI_PROJECT_DIR}/build/apps/cu/srscu /tmp/srscu - mv ${CI_PROJECT_DIR}/build/apps/du/srsdu /tmp/srsdu + find ${CI_PROJECT_DIR}/build -name "gnb" -type f -executable -exec mv {} /tmp/gnb \; + find ${CI_PROJECT_DIR}/build -name "srscu" -type f -executable -exec mv {} /tmp/srscu \; + find ${CI_PROJECT_DIR}/build -name "srsdu" -type f -executable -exec mv {} /tmp/srsdu \; cd build make clean mv /tmp/gnb ${CI_PROJECT_DIR}/build/apps/gnb/gnb @@ -229,17 +239,17 @@ variables: .smoke relwithdeb: extends: .build_and_unit - script: &smoke_script - - | - if [[ $CI_MERGE_REQUEST_LABELS != *"ci-no-plugin"* ]]; then - git submodule add https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_SHELL_SSH_HOST}/${PLUGIN_REPO}.git ${PLUGIN_PATH} - fi + script: - *srs_functions - build_srsgnb + - | + rm ${CI_PROJECT_DIR}/build/apps/gnb_split_7_2/gnb + rm ${CI_PROJECT_DIR}/build/apps/gnb_split_8/gnb - launch_tests variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: ubuntu-24.04 - COMPILER: gcc + COMPILER: clang CMAKE_BUILD_TYPE: RelWithDebInfo ASSERT_LEVEL: PARANOID TEST_MODE: default @@ -248,10 +258,9 @@ variables: .smoke tsan: extends: .build_and_unit - script: - - *smoke_script variables: - OS: ubuntu-24.04 + PLUGIN_BRANCH: $MR_PLUGIN_REF + OS: ubuntu-24.10 COMPILER: clang CMAKE_BUILD_TYPE: Debug ASSERT_LEVEL: PARANOID @@ -274,6 +283,7 @@ variables: .smoke archlinux: extends: .build_and_unit variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: archlinux-latest ENABLE_WERROR: "False" COMPILER: gcc @@ -285,11 +295,10 @@ variables: .smoke dpdk: extends: .build_and_unit - script: - - *smoke_script variables: - OS: ubuntu-24.04 - COMPILER: clang + PLUGIN_BRANCH: $MR_PLUGIN_REF + OS: ubuntu-22.04 + COMPILER: gcc CMAKE_BUILD_TYPE: Release ENABLE_UHD: "False" ENABLE_ZEROMQ: "False" @@ -302,6 +311,7 @@ variables: .smoke valgrind: extends: .build_and_unit variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: ubuntu-24.04 COMPILER: gcc CMAKE_BUILD_TYPE: Debug @@ -312,9 +322,8 @@ variables: .smoke avx512: extends: .build_and_unit - script: - - *smoke_script variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: ubuntu-24.04 COMPILER: gcc CMAKE_BUILD_TYPE: Release @@ -325,9 +334,8 @@ variables: .smoke arm: extends: .build_and_unit - script: - - *smoke_script variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: ubuntu-24.04 COMPILER: gcc CMAKE_BUILD_TYPE: Release @@ -338,9 +346,8 @@ variables: .smoke arm neon: extends: .build_and_unit - script: - - *smoke_script variables: + PLUGIN_BRANCH: $MR_PLUGIN_REF OS: ubuntu-24.04 COMPILER: gcc CMAKE_BUILD_TYPE: Release @@ -626,7 +633,7 @@ check builders ubuntu versions: extends: .check image exists for all supported ubuntu versions stage: static rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed start_in: 15 minutes variables: @@ -639,7 +646,7 @@ check builders ubuntu versions: smoke release update cache: extends: .smoke release rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -655,7 +662,7 @@ smoke release update cache: smoke relwithdeb update cache: extends: .smoke relwithdeb rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -666,7 +673,7 @@ smoke relwithdeb update cache: smoke tsan update cache: extends: .smoke tsan rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes interruptible: false @@ -677,7 +684,7 @@ smoke tsan update cache: smoke rhel update cache: extends: .smoke rhel rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -688,7 +695,7 @@ smoke rhel update cache: smoke archlinux update cache: extends: .smoke archlinux rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -699,7 +706,7 @@ smoke archlinux update cache: smoke dpdk update cache: extends: .smoke dpdk rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -710,7 +717,7 @@ smoke dpdk update cache: smoke valgrind update cache: extends: .smoke valgrind rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes interruptible: false @@ -726,7 +733,7 @@ smoke valgrind update cache: smoke avx512 update cache: extends: .smoke avx512 rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes interruptible: false @@ -737,7 +744,7 @@ smoke avx512 update cache: smoke arm update cache: extends: .smoke arm rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -748,7 +755,7 @@ smoke arm update cache: smoke arm neon update cache: extends: .smoke arm neon rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests Plugin/ when: delayed start_in: 45 minutes retry: 2 @@ -780,7 +787,7 @@ package: extends: .deb-package stage: build and unit tests rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests OpenSource/ when: delayed start_in: 45 minutes variables: &package_variables @@ -805,7 +812,7 @@ install-package: extends: .deb-install stage: build and unit tests rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests OpenSource/ variables: PROJECT_NAME: srsran-project RELEASE_VERSION: "99.9" @@ -824,7 +831,7 @@ check package ubuntu versions: extends: .check packaging ubuntu images stage: static rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ + - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed start_in: 15 minutes @@ -888,13 +895,16 @@ ubuntu-24.04 amd64 avx2: parallel: matrix: - <<: *basic_combinations + - <<: *basic_combinations + ENABLE_FFTW: "False" + ENABLE_MKL: "True" ubuntu-24.04 amd64 avx512: extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ when: delayed - start_in: 135 minutes + start_in: 165 minutes interruptible: false variables: OS: ubuntu-24.04 @@ -903,13 +913,16 @@ ubuntu-24.04 amd64 avx512: parallel: matrix: - <<: *basic_combinations + - <<: *basic_combinations + ENABLE_FFTW: "False" + ENABLE_MKL: "True" ubuntu-22.04 amd64 avx2: extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ when: delayed - start_in: 165 minutes + start_in: 195 minutes interruptible: false variables: OS: ubuntu-22.04 @@ -924,7 +937,7 @@ rhel-8 amd64 avx2: rules: - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ when: delayed - start_in: 195 minutes + start_in: 225 minutes interruptible: false variables: OS: rhel-8 @@ -950,6 +963,9 @@ ubuntu-24.04 arm neon: parallel: matrix: - <<: *basic_combinations + - <<: *basic_combinations + ENABLE_FFTW: "False" + ENABLE_ARMPL: "True" # Basic DPDK @@ -958,7 +974,7 @@ ubuntu dpdk: rules: - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ when: delayed - start_in: 225 minutes + start_in: 260 minutes interruptible: false variables: ENABLE_UHD: "False" @@ -1294,6 +1310,9 @@ ubuntu-24.04 amd64 native: parallel: matrix: - <<: *basic_combinations + - <<: *basic_combinations + ENABLE_FFTW: "False" + ENABLE_MKL: "True" ubuntu-24.04 arm native: extends: .build_and_unit @@ -1308,13 +1327,16 @@ ubuntu-24.04 arm native: parallel: matrix: - <<: *basic_combinations + - <<: *basic_combinations + ENABLE_FFTW: "False" + ENABLE_ARMPL: "True" ubuntu-22.04 amd64 native: extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 90 minutes + start_in: 120 minutes interruptible: false variables: OS: ubuntu-22.04 @@ -1328,7 +1350,7 @@ ubuntu-22.04 amd64 avx512: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 90 minutes + start_in: 120 minutes interruptible: false variables: OS: ubuntu-22.04 @@ -1343,7 +1365,7 @@ ubuntu-22.04 arm native: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 90 minutes + start_in: 120 minutes interruptible: false variables: OS: ubuntu-22.04 @@ -1357,7 +1379,7 @@ ubuntu-22.04 arm neon: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 90 minutes + start_in: 120 minutes interruptible: false variables: OS: ubuntu-22.04 @@ -1372,7 +1394,7 @@ rhel-8 amd64 native: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 120 minutes + start_in: 150 minutes interruptible: false variables: OS: rhel-8 @@ -1386,7 +1408,7 @@ rhel-8 amd64 avx512: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 120 minutes + start_in: 150 minutes interruptible: false variables: OS: rhel-8 @@ -1401,7 +1423,7 @@ rhel-8 arm native: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 120 minutes + start_in: 150 minutes interruptible: false variables: OS: rhel-8 @@ -1415,7 +1437,7 @@ rhel-8 arm neon: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 120 minutes + start_in: 150 minutes interruptible: false variables: OS: rhel-8 @@ -1481,13 +1503,17 @@ ubuntu-24.04 amd64 avx2 dpdk: matrix: - <<: *basic_combinations_dpdk DPDK_VERSION: "23.11.1_avx2" + - <<: *basic_combinations + DPDK_VERSION: "23.11.1_avx2" + ENABLE_FFTW: "False" + ENABLE_MKL: "True" ubuntu-24.04 amd64 avx512 dpdk: extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 270 minutes + start_in: 320 minutes interruptible: false variables: OS: ubuntu-24.04 @@ -1497,13 +1523,17 @@ ubuntu-24.04 amd64 avx512 dpdk: matrix: - <<: *basic_combinations_dpdk DPDK_VERSION: "23.11.1_avx512" + - <<: *basic_combinations + DPDK_VERSION: "23.11.1_avx512" + ENABLE_FFTW: "False" + ENABLE_MKL: "True" ubuntu-24.10 amd64 avx2 dpdk: extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 300 minutes + start_in: 360 minutes interruptible: false variables: OS: ubuntu-24.10 @@ -1519,7 +1549,7 @@ ubuntu-24.10 amd64 avx512 dpdk: rules: - if: $CI_DESCRIPTION =~ /Weekly/ when: delayed - start_in: 300 minutes + start_in: 360 minutes interruptible: false variables: OS: ubuntu-24.10 @@ -1530,121 +1560,134 @@ ubuntu-24.10 amd64 avx512 dpdk: - <<: *basic_combinations_dpdk DPDK_VERSION: "23.11.1_avx512" -############# -# Run check # -############# - -check-affinity-manager-nocpu: - image: ${CR_REGISTRY_URI}/srsgnb/builder-ubuntu-24.04:${DOCKER_BUILDER_VERSION} - stage: build and unit tests - needs: - - job: "smoke release update cache" - artifacts: true - - job: builder version - optional: false - artifacts: true - rules: - - if: $CI_DESCRIPTION =~ /Nightly Build Unit Tests/ - timeout: 15 minutes - tags: [amd64] - script: - - cd build/apps/gnb - - | - error_found=false - for (( i=3; i<=10; i++ )) - do - echo "Checking for number of CPU = $((i-2))" - output=$(taskset -c 2-$i bash -c "./gnb log --filename=stdout" 2>&1 || true) - echo "$output" - - if [[ "$output" == *"nof_cores_for_non_prio_threads"* ]]; then - error_found=true - echo "**********************************************************************************" - echo $output - echo "**********************************************************************************" - echo "Error for number of CPU = $((i-2))" - echo -e "\n\n\n\n\n" - fi - done - - if [ "$error_found" = true ]; then - exit 1 - fi - ############### # E2E Nightly # ############### +basic package: + extends: .deb-package + stage: build and unit tests + rules: + - if: $CI_DESCRIPTION =~ /Nightly E2E Tests OpenSource/ + retry: 2 + interruptible: false + variables: + <<: *package_variables + OS_VERSION: "24.04" + INFRASTRUCTURE_TAG: amd64-avx2 + needs: [] + basic relwithdeb: - extends: .smoke relwithdeb + extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly E2E Tests/ retry: 2 interruptible: false variables: + SRS_TARGET: srscu srsdu gnb + OS: ubuntu-24.04 + COMPILER: gcc + CMAKE_BUILD_TYPE: RelWithDebInfo + ASSERT_LEVEL: PARANOID TEST_MODE: none + MARCH: x86-64-v3 + INFRASTRUCTURE_TAG: amd64-avx2 after_script: - *build_after_script artifacts: <<: *build_artifacts - expire_in: 3 day + expire_in: 3 days -basic package: - extends: .deb-package - stage: build and unit tests +basic tsan: + extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly E2E Tests/ retry: 2 interruptible: false variables: - <<: *package_variables - OS_VERSION: "24.04" + SRS_TARGET: gnb + OS: ubuntu-24.04 + COMPILER: clang + CMAKE_BUILD_TYPE: Debug + ASSERT_LEVEL: PARANOID + ENABLE_TSAN: "True" + TEST_MODE: none + MARCH: x86-64-v3 INFRASTRUCTURE_TAG: amd64-avx2 - needs: [] + after_script: + - *build_after_script + artifacts: + <<: *build_artifacts + expire_in: 3 days -basic tsan: - extends: .smoke tsan +basic asan: + extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly E2E Tests/ retry: 2 interruptible: false variables: + SRS_TARGET: gnb + OS: ubuntu-24.04 + COMPILER: clang + CMAKE_BUILD_TYPE: Debug + ASSERT_LEVEL: PARANOID + ENABLE_ASAN: "True" TEST_MODE: none + MARCH: x86-64-v3 + INFRASTRUCTURE_TAG: amd64-avx2 + BUILD_ARGS: -DEXIT_TIMEOUT=60 after_script: - *build_after_script artifacts: <<: *build_artifacts - expire_in: 3 day + expire_in: 3 days -basic asan: - extends: smoke asan +basic memcheck: + extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly E2E Tests/ retry: 2 interruptible: false variables: + SRS_TARGET: gnb + OS: ubuntu-24.04 + COMPILER: gcc + CMAKE_BUILD_TYPE: Debug + ASSERT_LEVEL: PARANOID TEST_MODE: none MARCH: x86-64-v3 - BUILD_ARGS: -DEXIT_TIMEOUT=60 INFRASTRUCTURE_TAG: amd64-avx2 after_script: - *build_after_script artifacts: <<: *build_artifacts - expire_in: 3 day + expire_in: 3 days -basic memcheck: - extends: .smoke valgrind +basic ru dummy: + extends: .build_and_unit rules: - if: $CI_DESCRIPTION =~ /Nightly E2E Tests/ retry: 2 interruptible: false variables: + SRS_TARGET: gnb + OS: ubuntu-24.04 + COMPILER: gcc + CMAKE_BUILD_TYPE: Release TEST_MODE: none + ENABLE_UHD: "False" + ENABLE_ZEROMQ: "False" + ENABLE_DPDK: "True" + DPDK_VERSION: "23.11.1_avx512" + MARCH: x86-64-v4 + FORCE_DEBUG_INFO: "True" + ASSERT_LEVEL: AUTO + INFRASTRUCTURE_TAG: amd64-avx2-avx512 after_script: - *build_after_script artifacts: <<: *build_artifacts - expire_in: 3 day + expire_in: 3 days basic avx512 dpdk: extends: .build_and_unit @@ -1654,6 +1697,7 @@ basic avx512 dpdk: retry: 2 interruptible: false variables: + SRS_TARGET: gnb_split_7_2 OS: ubuntu-24.04 COMPILER: gcc CMAKE_BUILD_TYPE: Release @@ -1670,7 +1714,7 @@ basic avx512 dpdk: - *build_after_script artifacts: <<: *build_artifacts - expire_in: 3 day + expire_in: 3 days basic avx512 dpdk withassert: extends: basic avx512 dpdk diff --git a/.gitlab/ci/builders/codechecker/Dockerfile b/.gitlab/ci/builders/codechecker/Dockerfile index c30c3d1636..d830881d1b 100644 --- a/.gitlab/ci/builders/codechecker/Dockerfile +++ b/.gitlab/ci/builders/codechecker/Dockerfile @@ -16,4 +16,5 @@ ADD install_dependencies.sh /usr/local/bin RUN chmod +x /usr/local/bin/install_dependencies.sh RUN DEBIAN_FRONTEND=noninteractive apt-get update && install_dependencies.sh \ + && apt-get install -y --no-install-recommends git \ && apt-get autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/.gitlab/ci/e2e.yml b/.gitlab/ci/e2e.yml index 878589753c..ceb171cd0a 100644 --- a/.gitlab/ci/e2e.yml +++ b/.gitlab/ci/e2e.yml @@ -79,6 +79,7 @@ custom e2e: - *retina-needs - custom build - build trx driver + interruptible: false # Web jobs are standalone ################################################################################ # ZMQ @@ -163,6 +164,8 @@ amari 1UE 4x4 mimo: amari 4UE deb: extends: .zmq + rules: + - if: $CI_DESCRIPTION =~ /Nightly E2E Tests OpenSource/ variables: TESTBED: "zmq_deb" MARKERS: "smoke" @@ -214,6 +217,7 @@ amari 8UE: variables: MARKERS: "zmq and not smoke" RETINA_PARAM_ARGS: "gnb.all.pcap=True gnb.all.rlc_enable=False gnb.all.enable_integrity_protection=True" + timeout: 4h parallel: matrix: - KEYWORDS: @@ -293,6 +297,12 @@ cudu amari 64UE: RETINA_PARAM_ARGS: "gnb.all.pcap=True gnb.all.rlc_enable=False gnb.all.enable_integrity_protection=True" KEYWORDS: "ping and 64" +ric: + extends: .zmq + variables: + TESTBED: "zmq_ric" + MARKERS: "zmq_ric" + ################################################################################ # TEST MODE ################################################################################ @@ -335,7 +345,7 @@ test mode ru: E2E_LOG_LEVEL: "warning" RETINA_LAUNCHER_ARGS: "--retina-pod-timeout 900" needs: - - job: "basic avx512 dpdk" + - job: "basic ru dummy" artifacts: true - *txrx-lib - *retina-needs @@ -370,6 +380,16 @@ test mode ru memcheck: - *txrx-lib - *retina-needs +test mode ru acc100: + extends: test mode ru + rules: + - if: $CI_DESCRIPTION =~ /Nightly E2E Tests Plugin/ + variables: + TESTBED: "test_mode_acc100" + MARKERS: "test_mode_acc100" + KEYWORDS: "" + allow_failure: true + ################################################################################ # RF ################################################################################ @@ -453,6 +473,22 @@ android x300: artifacts: true - *retina-needs +android x300 drx: + stage: rf + extends: .e2e-run + allow_failure: true + variables: + GROUP: "rf" + TESTBED: "android_b200" + MARKERS: "android_drx" + E2E_LOG_LEVEL: "info" + KUBECONFIG_VAR_NAME: "RETINA_NAMESPACE_KUBECONFIG" + KUBECONFIG_VAR_NAME_EXTRA: "RETINA_NAMESPACE_KUBECONFIG_EXTRA" + needs: + - job: "basic relwithdeb" + artifacts: true + - *retina-needs + ################################################################################ # VIAVI ############################################################################### @@ -503,6 +539,7 @@ viavi-extended: viavi-extended-24h: extends: .viavi + timeout: 28 hours rules: - if: $CI_DESCRIPTION =~ /Weekly/ variables: @@ -538,7 +575,7 @@ retina setup: name: ${RETINA_REGISTRY_PREFIX}/launcher:${RETINA_VERSION} entrypoint: ["/bin/sh", "-c"] tags: - - "${RETINA_TAG}" + - retina-e2e-amd64 script: - | retina-garbage-collector --mode demolition @@ -568,4 +605,4 @@ retina runners: branch: main needs: - job: "retina setup" - artifacts: false \ No newline at end of file + artifacts: false diff --git a/.gitlab/ci/e2e/.env b/.gitlab/ci/e2e/.env index ed877f792c..d08a36cb8f 100644 --- a/.gitlab/ci/e2e/.env +++ b/.gitlab/ci/e2e/.env @@ -1,10 +1,11 @@ SRSGNB_REGISTRY_URI=registry.gitlab.com/softwareradiosystems/srsgnb RETINA_REGISTRY_PREFIX=registry.gitlab.com/softwareradiosystems/ci/retina -RETINA_VERSION=0.56.11 +RETINA_VERSION=0.59.0 UBUNTU_VERSION=24.04 AMARISOFT_VERSION=2023-09-08 SRSUE_VERSION=23.11 OPEN5GS_VERSION=2.7.0 +FLEXRIC_VERSION=br-flexric PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin METRICS_SERVER_VERSION=1.7.3 DPDK_VERSION=23.11.1 diff --git a/.gitlab/ci/e2e/retina_request_android_callbox.yml b/.gitlab/ci/e2e/retina_request_android_callbox.yml index 93971c9554..707c890f67 100644 --- a/.gitlab/ci/e2e/retina_request_android_callbox.yml +++ b/.gitlab/ci/e2e/retina_request_android_callbox.yml @@ -38,7 +38,7 @@ ephemeral-storage: requests: "6G" limits: "6G" - labels: + labels: - kubernetes.io/hostname=sdr6 resources: - type: sdr diff --git a/.gitlab/ci/e2e/retina_request_android_n300.yml b/.gitlab/ci/e2e/retina_request_android_n300.yml index 5fb8cf921f..f8a3b77082 100644 --- a/.gitlab/ci/e2e/retina_request_android_n300.yml +++ b/.gitlab/ci/e2e/retina_request_android_n300.yml @@ -38,7 +38,7 @@ ephemeral-storage: requests: "6G" limits: "6G" - labels: + labels: - kubernetes.io/hostname=sdr6 resources: - type: sdr diff --git a/.gitlab/ci/e2e/retina_request_android_x300.yml b/.gitlab/ci/e2e/retina_request_android_x300.yml index ad8cda89bb..1980886b2d 100644 --- a/.gitlab/ci/e2e/retina_request_android_x300.yml +++ b/.gitlab/ci/e2e/retina_request_android_x300.yml @@ -38,7 +38,7 @@ ephemeral-storage: requests: "6G" limits: "6G" - labels: + labels: - kubernetes.io/hostname=sdr6 resources: - type: sdr diff --git a/.gitlab/ci/e2e/retina_request_test_mode.yml b/.gitlab/ci/e2e/retina_request_test_mode.yml index d12c59a545..840e156f9b 100644 --- a/.gitlab/ci/e2e/retina_request_test_mode.yml +++ b/.gitlab/ci/e2e/retina_request_test_mode.yml @@ -9,7 +9,7 @@ - name: srs-gnb type: gnb image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} requirements: arch: amd64 @@ -42,5 +42,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_test_mode_acc100.yml b/.gitlab/ci/e2e/retina_request_test_mode_acc100.yml new file mode 100644 index 0000000000..c3c7595b4c --- /dev/null +++ b/.gitlab/ci/e2e/retina_request_test_mode_acc100.yml @@ -0,0 +1,49 @@ +# +# Copyright 2013-2024 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +- name: srs-gnb + type: gnb + image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} + requirements: + arch: amd64 + cpu: + requests: 20 + limits: 20 + memory: + requests: "60G" + limits: "60G" + ephemeral-storage: + requests: "6G" + limits: "6G" + hugepages-1Gi: + requests: 8Gi + limits: 8Gi + resources: + - type: accelerator + model: acc100 + environment: + - PATH: ${PATH}:/builds/softwareradiosystems/srsgnb/build/apps/gnb + shared_files: + - local_path: ${GNB_BINARY_PATH} + remote_path: /usr/local/bin/gnb + is_executable: true + +- name: open5gs + type: 5gc + requirements: + arch: amd64 + cpu: + requests: 1 + memory: + requests: "4G" + ephemeral-storage: + requests: "6G" + limits: "6G" + image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_zmq.yml b/.gitlab/ci/e2e/retina_request_zmq.yml index 3695b2bb00..0956886350 100644 --- a/.gitlab/ci/e2e/retina_request_zmq.yml +++ b/.gitlab/ci/e2e/retina_request_zmq.yml @@ -9,7 +9,7 @@ - name: amarisoft-ue type: ue image: ${RETINA_REGISTRY_PREFIX}/amarisoftue:${AMARISOFT_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} nof_ports: 64 requirements: @@ -35,7 +35,7 @@ - name: srs-gnb type: gnb image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} requirements: arch: amd64 @@ -71,5 +71,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_zmq_cudu.yml b/.gitlab/ci/e2e/retina_request_zmq_cudu.yml index 8aa9318a95..7423499970 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_cudu.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_cudu.yml @@ -9,7 +9,7 @@ - name: amarisoft-ue type: ue image: ${RETINA_REGISTRY_PREFIX}/amarisoftue:${AMARISOFT_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} nof_ports: 64 requirements: @@ -35,7 +35,7 @@ - name: srs-gnb type: gnb image: ${RETINA_REGISTRY_PREFIX}/srscudu:${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} requirements: arch: amd64 @@ -72,5 +72,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_zmq_deb.yml b/.gitlab/ci/e2e/retina_request_zmq_deb.yml index 253f1910c5..22f56fbdef 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_deb.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_deb.yml @@ -9,7 +9,7 @@ - name: amarisoft-ue type: ue image: ${RETINA_REGISTRY_PREFIX}/amarisoftue:${AMARISOFT_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} nof_ports: 4 requirements: @@ -35,7 +35,7 @@ - name: srs-gnb type: gnb image: ${RETINA_REGISTRY_PREFIX}/agent-ubuntu-${UBUNTU_VERSION}:${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} requirements: arch: amd64 @@ -69,5 +69,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_zmq_ric.yml b/.gitlab/ci/e2e/retina_request_zmq_ric.yml new file mode 100644 index 0000000000..0049e7b907 --- /dev/null +++ b/.gitlab/ci/e2e/retina_request_zmq_ric.yml @@ -0,0 +1,87 @@ +# +# Copyright 2013-2024 Software Radio Systems Limited +# +# By using this file, you agree to the terms and conditions set +# forth in the LICENSE file which can be found at the top level of +# the distribution. +# + +- name: srs-ue + type: ue + image: ${RETINA_REGISTRY_PREFIX}/srsue:${SRSUE_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_0} + requirements: + arch: amd64 + cpu: + requests: 4 + limits: 4 + memory: + requests: "22G" + limits: "22G" + ephemeral-storage: + requests: "6G" + limits: "6G" + resources: + - type: zmq + environment: + - RETINA_AGENT_ARGS: --grpc.max_size_file_artifact 209715200 + +- name: srs-gnb + type: gnb + image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_0} + requirements: + arch: amd64 + cpu: + requests: 4 + limits: 4 + memory: + requests: "22G" + limits: "22G" + ephemeral-storage: + requests: "15G" + limits: "15G" + resources: + - type: zmq + environment: + - PATH: ${PATH}:/builds/softwareradiosystems/srsgnb/build/apps/gnb + shared_files: + - local_path: ${GNB_BINARY_PATH} + remote_path: /usr/local/bin/gnb + is_executable: true + +- name: open5gs + type: 5gc + requirements: + arch: amd64 + cpu: + requests: 1 + limits: 1 + memory: + requests: "8G" + limits: "8G" + ephemeral-storage: + requests: "6G" + limits: "6G" + image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_0} + +- name: flexric-ric + type: ric + requirements: + arch: amd64 + cpu: + requests: 1 + limits: 1 + memory: + requests: "8G" + limits: "8G" + ephemeral-storage: + requests: "6G" + limits: "6G" + image: ${RETINA_REGISTRY_PREFIX}/flexric:${FLEXRIC_VERSION}_${RETINA_VERSION} + labels: + - ${ZMQ_HOSTLABEL_0} diff --git a/.gitlab/ci/e2e/retina_request_zmq_smoke.yml b/.gitlab/ci/e2e/retina_request_zmq_smoke.yml index 65c52c8dd7..66292d777a 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_smoke.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_smoke.yml @@ -9,7 +9,7 @@ - name: amarisoft-ue type: ue image: ${RETINA_REGISTRY_PREFIX}/amarisoftue:${AMARISOFT_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_1} nof_ports: 4 requirements: @@ -35,7 +35,7 @@ - name: srs-gnb type: gnb image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_1} requirements: arch: amd64 @@ -71,5 +71,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: + labels: - ${ZMQ_HOSTLABEL_1} diff --git a/.gitlab/ci/e2e/retina_request_zmq_uesim.yml b/.gitlab/ci/e2e/retina_request_zmq_uesim.yml index df4b3ea154..0a0a05f5aa 100644 --- a/.gitlab/ci/e2e/retina_request_zmq_uesim.yml +++ b/.gitlab/ci/e2e/retina_request_zmq_uesim.yml @@ -10,7 +10,7 @@ type: ue image: ${RETINA_REGISTRY_PREFIX}/amarisoftue-remote:${RETINA_VERSION} taints: ["machine=srskit2"] - labels: + labels: - ${ZMQ_HOSTLABEL_1} nof_ports: 32 requirements: @@ -37,7 +37,7 @@ type: gnb image: ${RETINA_REGISTRY_PREFIX}/srsgnb:${RETINA_VERSION} taints: ["machine=srskit2"] - labels: + labels: - ${ZMQ_HOSTLABEL_1} force_external_ip: true requirements: @@ -74,5 +74,5 @@ requests: "6G" limits: "6G" image: ${RETINA_REGISTRY_PREFIX}/open5gs:${OPEN5GS_VERSION}_${RETINA_VERSION} - labels: - - ${ZMQ_HOSTLABEL_1} \ No newline at end of file + labels: + - ${ZMQ_HOSTLABEL_1} diff --git a/.gitlab/ci/release.yml b/.gitlab/ci/release.yml index 0f14bb7549..d87748d559 100644 --- a/.gitlab/ci/release.yml +++ b/.gitlab/ci/release.yml @@ -148,6 +148,7 @@ update private branch: - git config --global filter.lfs.process "git-lfs filter-process --skip" - git config --global user.name "${CODEBOT_USERNAME}" - git config --global user.email "${CODEBOT_LONG_USERNAME}@noreply.gitlab.com" + - git config --global url."https://${PUBLIC_PUSH_TOKEN}@github.com/".insteadOf "git@github.com:" script: - git clone https://${CODEBOT_USERNAME}:${CODEBOT_TOKEN}@gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}.git ${CI_PROJECT_DIR} - cd ${CI_PROJECT_DIR} @@ -283,19 +284,21 @@ publish main: optional: false artifacts: false -publish maltab: +publish matlab: stage: public interruptible: false - variables: - ON_SCHEDULE: "true" - CI_DESCRIPTION: Release - NOTIFY_SLACK: "false" - SRSRAN_REPO_URL: https://${PUBLIC_PUSH_TOKEN}@github.com/${PUBLIC_REPO}.git - SRSRAN_COMMIT: $PUBLIC_BRANCH - DOCKER_BUILDER_VERSION: $DOCKER_BUILDER_VERSION rules: - if: *on_public_push + variables: &matlab_release_var + ON_SCHEDULE: "true" + CI_DESCRIPTION: Release + NOTIFY_SLACK: "false" + SRSRAN_REPO_URL: https://${PUBLIC_PUSH_TOKEN}@github.com/${PUBLIC_REPO}.git + SRSRAN_COMMIT: main + DOCKER_BUILDER_VERSION: $DOCKER_BUILDER_VERSION - if: *on_public_release + variables: + <<: *matlab_release_var needs: - job: publish main - job: builder version diff --git a/.gitlab/ci/schedules.yml b/.gitlab/ci/schedules.yml index 9575b2e105..ce0ce3d78e 100644 --- a/.gitlab/ci/schedules.yml +++ b/.gitlab/ci/schedules.yml @@ -20,14 +20,14 @@ Rebaser: value: "false" raw: false -Nightly Build Unit Tests: - cron: "45 23 * * 0-5" +Nightly Build Unit Tests Plugin: + cron: "45 23 * * 1-5" cron_timezone: "Europe/Madrid" ref: refs/heads/dev variables: - variable_type: env_var key: CI_DESCRIPTION - value: "Nightly Build Unit Tests" + value: "Nightly Build Unit Tests Plugin" raw: false - variable_type: env_var key: NOTIFY_SLACK @@ -37,15 +37,19 @@ Nightly Build Unit Tests: key: SLACK_CHANNEL_OK value: "#ci_gnb" raw: false + - variable_type: env_var + key: PLUGIN_BRANCH + value: "main" + raw: false -Nightly E2E Tests: - cron: "30 22 * * 0-5" +Nightly E2E Tests Plugin: + cron: "30 22 * * 1-5" cron_timezone: "Europe/Madrid" ref: refs/heads/dev variables: - variable_type: env_var key: CI_DESCRIPTION - value: "Nightly E2E Tests" + value: "Nightly E2E Tests Plugin" raw: false - variable_type: env_var key: NOTIFY_SLACK @@ -55,6 +59,10 @@ Nightly E2E Tests: key: SLACK_CHANNEL_OK value: "#ci_gnb" raw: false + - variable_type: env_var + key: PLUGIN_BRANCH + value: "main" + raw: false Weekly: cron: "00 13 * * 6" @@ -73,6 +81,10 @@ Weekly: key: SLACK_CHANNEL_OK value: "#ci_gnb" raw: false + - variable_type: env_var + key: PLUGIN_BRANCH + value: "main" + raw: false Alternative OSs: cron: "00 10 * * 6" @@ -91,3 +103,43 @@ Alternative OSs: key: SLACK_CHANNEL_OK value: "#ci_gnb" raw: false + - variable_type: env_var + key: PLUGIN_BRANCH + value: "main" + raw: false + +Nightly Build Unit Tests OpenSource: + cron: "45 23 * * 0" + cron_timezone: "Europe/Madrid" + ref: refs/heads/dev + variables: + - variable_type: env_var + key: CI_DESCRIPTION + value: "Nightly Build Unit Tests OpenSource" + raw: false + - variable_type: env_var + key: NOTIFY_SLACK + value: "true" + raw: false + - variable_type: env_var + key: SLACK_CHANNEL_OK + value: "#ci_gnb" + raw: false + +Nightly E2E Tests OpenSource: + cron: "30 22 * * 0" + cron_timezone: "Europe/Madrid" + ref: refs/heads/dev + variables: + - variable_type: env_var + key: CI_DESCRIPTION + value: "Nightly E2E Tests OpenSource" + raw: false + - variable_type: env_var + key: NOTIFY_SLACK + value: "true" + raw: false + - variable_type: env_var + key: SLACK_CHANNEL_OK + value: "#ci_gnb" + raw: false diff --git a/.gitlab/run_custom_pipeline.py b/.gitlab/run_custom_pipeline.py index d51dd95c97..64b4bb4e4c 100755 --- a/.gitlab/run_custom_pipeline.py +++ b/.gitlab/run_custom_pipeline.py @@ -11,7 +11,7 @@ GITLAB_URL = "https://gitlab.com" NEEDS_REGEX = re.compile(r"Downloading artifacts for .* \((\d+)\)...", flags=re.MULTILINE) -VARIABLE_REGEX = re.compile(r"^(\w+)=(.*)?$", flags=re.MULTILINE) +VARIABLE_REGEX = re.compile(r"^(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z \d{2}O )?(\w+)=(.*)?$", flags=re.MULTILINE) try: @@ -22,7 +22,7 @@ sys.exit(1) -def _parse_args() -> Tuple[str, str, str, str, int, bool]: +def _parse_args() -> Tuple[str, str, str, str, str, int, bool]: parser = argparse.ArgumentParser(description="Run a custom pipeline") parser.add_argument( "--token", @@ -44,6 +44,13 @@ def _parse_args() -> Tuple[str, str, str, str, int, bool]: type=str, help='Job name. Please use "" when the job name contains spaces', ) + parser.add_argument( + "--plugin-branch", + required=False, + type=str, + help="Remote branch for plugin repository. Set to '' to skip plugin repository.", + default="", + ) parser.add_argument( "--timeout", required=False, type=int, default=300, help="Search for job timeout (default: %(default)s)" ) @@ -51,7 +58,7 @@ def _parse_args() -> Tuple[str, str, str, str, int, bool]: "--dryrun", action="store_true", help="Search the job but skip pipeline creation (default: false)" ) args = parser.parse_args() - return args.token, args.project, args.branch, args.job.strip(), args.timeout, args.dryrun + return args.token, args.project, args.branch, args.plugin_branch, args.job.strip(), args.timeout, args.dryrun def _get_project(token: str, instance: str, project: str) -> Project: @@ -114,9 +121,10 @@ def main(): Entrypoint runner. """ try: - token, project_name, branch, job, timeout, dryrun = _parse_args() + token, project_name, branch, plugin_branch, job, timeout, dryrun = _parse_args() project = _get_project(token=token, instance=GITLAB_URL, project=project_name) variable_dict = _search_job_by_name(project=project, job_name=job, timeout=timeout) + variable_dict["PLUGIN_BRANCH"] = plugin_branch _create_pipeline(project=project, branch=branch, variables=variable_dict, dryrun=dryrun) except KeyboardInterrupt: print() diff --git a/COPYRIGHT b/COPYRIGHT index 3cc7d7e778..3698d7c838 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -16,22 +16,24 @@ Copyright: 2013-2017 Google Inc. License: MIT -Files: external/fmt/chrono.h +Files: external/fmt/args.h + external/fmt/base.h + external/fmt/chrono.h external/fmt/color.h external/fmt/compile.h external/fmt/core.h external/fmt/format.h external/fmt/format-inl.h external/fmt/LICENSE.rst - external/fmt/locale.h external/fmt/os.h external/fmt/ostream.h - external/fmt/posix.h external/fmt/printf.h external/fmt/ranges.h + external/fmt/std.h + external/fmt/xchar.h external/fmt/format.cc external/fmt/os.cc -Copyright: 2012-2020 Victor Zverovich +Copyright: 2012-2024 Victor Zverovich License: MIT @@ -91,13 +93,13 @@ License: BSD-2-clause (Simplified BSD) modification, are permitted provided that the following conditions are met: . - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR diff --git a/apps/cu/CMakeLists.txt b/apps/cu/CMakeLists.txt index 904fd427dd..990ebf704e 100644 --- a/apps/cu/CMakeLists.txt +++ b/apps/cu/CMakeLists.txt @@ -29,11 +29,11 @@ install(TARGETS srscu RUNTIME) target_link_libraries(srscu PRIVATE - srsran_app_services srsran_support srsran_versioning srsran_o_cu_cp_app_unit srsran_o_cu_up_app_unit + srsran_app_services srsran_ngap srsran_f1c_gateway srsran_e1_gateway diff --git a/apps/cu/cu.cpp b/apps/cu/cu.cpp index 18aa5295c6..1cbea42cf5 100644 --- a/apps/cu/cu.cpp +++ b/apps/cu/cu.cpp @@ -45,7 +45,7 @@ #include "srsran/gateways/udp_network_gateway.h" #include "srsran/gtpu/gtpu_config.h" #include "srsran/gtpu/gtpu_demux_factory.h" -#include "srsran/gtpu/ngu_gateway.h" +#include "srsran/gtpu/gtpu_gateway.h" #include "srsran/support/backtrace.h" #include "srsran/support/config_parsers.h" #include "srsran/support/cpu_features.h" @@ -83,7 +83,7 @@ static constexpr unsigned MAX_CONFIG_FILES = 10; static void populate_cli11_generic_args(CLI::App& app) { fmt::memory_buffer buffer; - format_to(buffer, "srsRAN 5G CU version {} ({})", get_version(), get_build_hash()); + format_to(std::back_inserter(buffer), "srsRAN 5G CU version {} ({})", get_version(), get_build_hash()); app.set_version_flag("-v,--version", srsran::to_c_str(buffer)); app.set_config("-c,", config_file, "Read config from file", false)->expected(1, MAX_CONFIG_FILES); } @@ -157,11 +157,11 @@ static void autoderive_cu_up_parameters_after_parsing(o_cu_up_unit_config& o const cu_cp_unit_config& cu_cp_cfg) { // If no UPF is configured, we set the UPF configuration from the CU-CP AMF configuration. - if (o_cu_up_cfg.cu_up_cfg.upf_cfg.bind_addr == "auto") { - o_cu_up_cfg.cu_up_cfg.upf_cfg.bind_addr = cu_cp_cfg.amf_config.amf.bind_addr; + if (o_cu_up_cfg.cu_up_cfg.ngu_cfg.ngu_socket_cfg.empty()) { + cu_up_unit_ngu_socket_config sock_cfg; + sock_cfg.bind_addr = cu_cp_cfg.amf_config.amf.bind_addr; + o_cu_up_cfg.cu_up_cfg.ngu_cfg.ngu_socket_cfg.push_back(sock_cfg); } - o_cu_up_cfg.cu_up_cfg.upf_cfg.no_core = cu_cp_cfg.amf_config.no_core; - o_cu_up_cfg.e2_cfg.pcaps.enabled = o_cu_up_cfg.e2_cfg.base_config.enable_unit_e2 && o_cu_up_cfg.e2_cfg.pcaps.enabled; } int main(int argc, char** argv) @@ -199,6 +199,7 @@ int main(int argc, char** argv) // Set the callback for the app calling all the autoderivation functions. app.callback([&app, &o_cu_cp_app_unit, &o_cu_up_app_unit]() { o_cu_cp_app_unit->on_configuration_parameters_autoderivation(app); + o_cu_up_app_unit->on_configuration_parameters_autoderivation(app); autoderive_cu_up_parameters_after_parsing(o_cu_up_app_unit->get_o_cu_up_unit_config(), o_cu_cp_app_unit->get_o_cu_cp_unit_config().cucp_cfg); @@ -284,7 +285,8 @@ int main(int argc, char** argv) f1c_sctp_cfg.bind_address = cu_cfg.f1ap_cfg.bind_addr; f1c_sctp_cfg.bind_port = F1AP_PORT; f1c_sctp_cfg.ppid = F1AP_PPID; - f1c_cu_sctp_gateway_config f1c_server_cfg({f1c_sctp_cfg, *epoll_broker, *cu_cp_dlt_pcaps.f1ap}); + f1c_cu_sctp_gateway_config f1c_server_cfg( + {f1c_sctp_cfg, *epoll_broker, *workers.non_rt_hi_prio_exec, *cu_cp_dlt_pcaps.f1ap}); std::unique_ptr cu_f1c_gw = srsran::create_f1c_gateway_server(f1c_server_cfg); // Create F1-U GW (TODO factory and cleanup). @@ -297,8 +299,8 @@ int main(int argc, char** argv) cu_f1u_gw_config.bind_port = GTPU_PORT; cu_f1u_gw_config.reuse_addr = false; cu_f1u_gw_config.pool_occupancy_threshold = cu_cfg.nru_cfg.pool_occupancy_threshold; - std::unique_ptr cu_f1u_gw = - srs_cu_up::create_udp_ngu_gateway(cu_f1u_gw_config, *epoll_broker, workers.cu_up_exec_mapper->io_ul_executor()); + std::unique_ptr cu_f1u_gw = create_udp_gtpu_gateway( + cu_f1u_gw_config, *epoll_broker, workers.cu_up_exec_mapper->io_ul_executor(), *workers.non_rt_low_prio_exec); std::unique_ptr cu_f1u_conn = srs_cu_up::create_split_f1u_gw( {*cu_f1u_gw, *cu_f1u_gtpu_demux, *cu_up_dlt_pcaps.f1u, GTPU_PORT, cu_cfg.nru_cfg.ext_addr}); @@ -306,23 +308,25 @@ int main(int argc, char** argv) std::unique_ptr e1_gw = create_e1_local_connector(e1_local_connector_config{*cu_up_dlt_pcaps.e1ap}); - // Create manager of timers for CU-CP and CU-UP, which will be - // driven by the system timer slot ticks. + // Create manager of timers for CU-CP and CU-UP, which will be driven by the system timer slot ticks. timer_manager app_timers{256}; timer_manager* cu_timers = &app_timers; - // Create time source that ticks the timers - io_timer_source time_source{app_timers, *epoll_broker, std::chrono::milliseconds{1}}; + // Create time source that ticks the timers. + std::optional time_source( + std::in_place_t{}, app_timers, *epoll_broker, *workers.non_rt_hi_prio_exec, std::chrono::milliseconds{1}); // Instantiate E2AP client gateway. std::unique_ptr e2_gw_cu_cp = create_e2_gateway_client( generate_e2_client_gateway_config(o_cu_cp_app_unit->get_o_cu_cp_unit_config().e2_cfg.base_config, *epoll_broker, + *workers.non_rt_hi_prio_exec, *cu_cp_dlt_pcaps.e2ap, E2_CP_PPID)); std::unique_ptr e2_gw_cu_up = create_e2_gateway_client( generate_e2_client_gateway_config(o_cu_up_app_unit->get_o_cu_up_unit_config().e2_cfg.base_config, *epoll_broker, + *workers.non_rt_hi_prio_exec, *cu_up_dlt_pcaps.e2ap, E2_UP_PPID)); @@ -330,20 +334,22 @@ int main(int argc, char** argv) // Create O-CU-CP dependencies. o_cu_cp_unit_dependencies o_cucp_deps; - o_cucp_deps.cu_cp_executor = workers.cu_cp_exec; - o_cucp_deps.cu_cp_e2_exec = workers.cu_e2_exec; - o_cucp_deps.timers = cu_timers; - o_cucp_deps.ngap_pcap = cu_cp_dlt_pcaps.ngap.get(); - o_cucp_deps.broker = epoll_broker.get(); - o_cucp_deps.e2_gw = e2_gw_cu_cp.get(); - o_cucp_deps.metrics_notifier = &metrics_notifier_forwarder; + o_cucp_deps.cu_cp_executor = workers.cu_cp_exec; + o_cucp_deps.cu_cp_n2_rx_executor = workers.non_rt_hi_prio_exec; + o_cucp_deps.cu_cp_e2_exec = workers.cu_e2_exec; + o_cucp_deps.timers = cu_timers; + o_cucp_deps.ngap_pcap = cu_cp_dlt_pcaps.ngap.get(); + o_cucp_deps.broker = epoll_broker.get(); + o_cucp_deps.e2_gw = e2_gw_cu_cp.get(); + o_cucp_deps.metrics_notifier = &metrics_notifier_forwarder; // Create O-CU-CP. auto o_cucp_unit = o_cu_cp_app_unit->create_o_cu_cp(o_cucp_deps); srs_cu_cp::o_cu_cp& o_cucp_obj = *o_cucp_unit.unit; // Create console helper object for commands and metrics printing. - app_services::stdin_command_dispatcher command_parser(*epoll_broker, o_cucp_unit.commands); + app_services::stdin_command_dispatcher command_parser( + *epoll_broker, *workers.non_rt_low_prio_exec, o_cucp_unit.commands); std::vector metrics_configs = std::move(o_cucp_unit.metrics); // Connect E1AP to O-CU-CP. @@ -398,6 +404,9 @@ int main(int argc, char** argv) // Stop O-CU-CP activity. o_cucp_obj.get_cu_cp().stop(); + // Stop the timer source before stopping the workers. + time_source.reset(); + // Close PCAPs cu_logger.info("Closing PCAP files..."); cu_cp_dlt_pcaps.close(); diff --git a/apps/cu/cu_appconfig_validator.cpp b/apps/cu/cu_appconfig_validator.cpp index 1b4c7c2d5c..fb0f6cac22 100644 --- a/apps/cu/cu_appconfig_validator.cpp +++ b/apps/cu/cu_appconfig_validator.cpp @@ -22,11 +22,20 @@ #include "cu_appconfig_validator.h" #include "apps/services/logger/logger_appconfig_validator.h" +#include "apps/services/worker_manager/worker_manager_appconfig_validator.h" #include "cu_appconfig.h" using namespace srsran; bool srsran::validate_cu_appconfig(const cu_appconfig& config) { - return validate_logger_appconfig(config.log_cfg); + if (!validate_logger_appconfig(config.log_cfg)) { + return false; + } + + if (!validate_expert_execution_appconfig(config.expert_execution_cfg)) { + return false; + } + + return true; } diff --git a/apps/du/adapters/f1_gateways.h b/apps/du/adapters/f1_gateways.h index 38e51c1e1f..95e519bb40 100644 --- a/apps/du/adapters/f1_gateways.h +++ b/apps/du/adapters/f1_gateways.h @@ -30,6 +30,7 @@ namespace srsran { std::unique_ptr create_f1c_client_gateway(const std::string& cu_cp_addr, const std::string& bind_addr, io_broker& broker, + task_executor& io_rx_executor, dlt_pcap& f1ap_pcap) { sctp_network_connector_config f1c_sctp{}; @@ -39,7 +40,7 @@ std::unique_ptr create_f1c_client_gateway(const s f1c_sctp.connect_port = F1AP_PORT; f1c_sctp.ppid = F1AP_PPID; f1c_sctp.bind_address = bind_addr; - return create_f1c_gateway_client(f1c_du_sctp_gateway_config{f1c_sctp, broker, f1ap_pcap}); + return create_f1c_gateway_client(f1c_du_sctp_gateway_config{f1c_sctp, broker, io_rx_executor, f1ap_pcap}); } } // namespace srsran diff --git a/apps/du/du.cpp b/apps/du/du.cpp index acc9c68ec7..c79760e599 100644 --- a/apps/du/du.cpp +++ b/apps/du/du.cpp @@ -75,7 +75,7 @@ static constexpr unsigned MAX_CONFIG_FILES = 10; static void populate_cli11_generic_args(CLI::App& app) { fmt::memory_buffer buffer; - format_to(buffer, "srsRAN 5G DU version {} ({})", get_version(), get_build_hash()); + format_to(std::back_inserter(buffer), "srsRAN 5G DU version {} ({})", get_version(), get_build_hash()); app.set_version_flag("-v,--version", srsran::to_c_str(buffer)); app.set_config("-c,", config_file, "Read config from file", false)->expected(1, MAX_CONFIG_FILES); } @@ -260,27 +260,31 @@ int main(int argc, char** argv) flexible_o_du_pcaps du_pcaps = create_o_du_pcaps(o_du_app_unit->get_o_du_high_unit_config(), workers); // Instantiate F1-C client gateway. - std::unique_ptr f1c_gw = create_f1c_client_gateway( - du_cfg.f1ap_cfg.cu_cp_address, du_cfg.f1ap_cfg.bind_address, *epoll_broker, *du_pcaps.f1ap); + std::unique_ptr f1c_gw = create_f1c_client_gateway(du_cfg.f1ap_cfg.cu_cp_address, + du_cfg.f1ap_cfg.bind_address, + *epoll_broker, + *workers.non_rt_hi_prio_exec, + *du_pcaps.f1ap); // Create manager of timers for DU, which will be driven by the PHY slot ticks. timer_manager app_timers{256}; - // Create F1-U connector + // Create F1-U connector. // TODO: Simplify this and use factory. - gtpu_demux_creation_request du_f1u_gtpu_msg = {}; - du_f1u_gtpu_msg.cfg.warn_on_drop = true; - du_f1u_gtpu_msg.gtpu_pcap = du_pcaps.f1u.get(); - std::unique_ptr du_f1u_gtpu_demux = create_gtpu_demux(du_f1u_gtpu_msg); - udp_network_gateway_config du_f1u_gw_config = {}; - du_f1u_gw_config.bind_address = du_cfg.nru_cfg.bind_address; - du_f1u_gw_config.bind_port = GTPU_PORT; - du_f1u_gw_config.reuse_addr = false; - du_f1u_gw_config.pool_occupancy_threshold = du_cfg.nru_cfg.pool_threshold; - std::unique_ptr du_f1u_gw = srs_cu_up::create_udp_ngu_gateway( - du_f1u_gw_config, - *epoll_broker, - workers.get_du_high_executor_mapper(0).ue_mapper().mac_ul_pdu_executor(to_du_ue_index(0))); + gtpu_demux_creation_request du_f1u_gtpu_msg = {}; + du_f1u_gtpu_msg.cfg.warn_on_drop = true; + du_f1u_gtpu_msg.gtpu_pcap = du_pcaps.f1u.get(); + std::unique_ptr du_f1u_gtpu_demux = create_gtpu_demux(du_f1u_gtpu_msg); + udp_network_gateway_config du_f1u_gw_config = {}; + du_f1u_gw_config.bind_address = du_cfg.nru_cfg.bind_address; + du_f1u_gw_config.bind_port = GTPU_PORT; + du_f1u_gw_config.reuse_addr = false; + du_f1u_gw_config.pool_occupancy_threshold = du_cfg.nru_cfg.pool_threshold; + std::unique_ptr du_f1u_gw = + create_udp_gtpu_gateway(du_f1u_gw_config, + *epoll_broker, + workers.get_du_high_executor_mapper(0).ue_mapper().mac_ul_pdu_executor(to_du_ue_index(0)), + *workers.non_rt_low_prio_exec); std::unique_ptr du_f1u_conn = srs_du::create_split_f1u_gw( {du_f1u_gw.get(), du_f1u_gtpu_demux.get(), *du_pcaps.f1u, GTPU_PORT, du_cfg.nru_cfg.ext_addr}); @@ -289,8 +293,12 @@ int main(int argc, char** argv) srslog::fetch_udp_sink(du_cfg.metrics_cfg.addr, du_cfg.metrics_cfg.port, srslog::create_json_formatter()); // Instantiate E2AP client gateway. - std::unique_ptr e2_gw = create_e2_gateway_client(generate_e2_client_gateway_config( - o_du_app_unit->get_o_du_high_unit_config().e2_cfg.base_cfg, *epoll_broker, *du_pcaps.e2ap, E2_DU_PPID)); + std::unique_ptr e2_gw = create_e2_gateway_client( + generate_e2_client_gateway_config(o_du_app_unit->get_o_du_high_unit_config().e2_cfg.base_cfg, + *epoll_broker, + *workers.non_rt_hi_prio_exec, + *du_pcaps.e2ap, + E2_DU_PPID)); app_services::metrics_notifier_proxy_impl metrics_notifier_forwarder; o_du_unit_dependencies du_dependencies; @@ -304,7 +312,7 @@ int main(int argc, char** argv) du_dependencies.json_sink = &json_sink; du_dependencies.metrics_notifier = &metrics_notifier_forwarder; - auto du_inst_and_cmds = o_du_app_unit->create_flexible_o_du_unit(du_dependencies, du_cfg.du_multicell_enabled); + auto du_inst_and_cmds = o_du_app_unit->create_flexible_o_du_unit(du_dependencies); // Only DU has metrics now. app_services::metrics_manager metrics_mngr( @@ -315,7 +323,8 @@ int main(int argc, char** argv) srs_du::du& du_inst = *du_inst_and_cmds.unit; // Register the commands. - app_services::stdin_command_dispatcher command_parser(*epoll_broker, du_inst_and_cmds.commands); + app_services::stdin_command_dispatcher command_parser( + *epoll_broker, *workers.non_rt_low_prio_exec, du_inst_and_cmds.commands); // Start processing. du_inst.get_power_controller().start(); diff --git a/apps/du/du_appconfig.h b/apps/du/du_appconfig.h index df833e883d..1e338cd140 100644 --- a/apps/du/du_appconfig.h +++ b/apps/du/du_appconfig.h @@ -58,8 +58,6 @@ struct metrics_appconfig { struct du_appconfig { /// Default constructor to update the log filename. du_appconfig() { log_cfg.filename = "/tmp/du.log"; } - /// DU multicell flag. - bool du_multicell_enabled = false; /// Loggers configuration. logger_appconfig log_cfg; /// Metrics configuration. diff --git a/apps/du/du_appconfig_cli11_schema.cpp b/apps/du/du_appconfig_cli11_schema.cpp index 2ff53ffaee..25a660293a 100644 --- a/apps/du/du_appconfig_cli11_schema.cpp +++ b/apps/du/du_appconfig_cli11_schema.cpp @@ -63,9 +63,6 @@ static void configure_cli11_f1u_args(CLI::App& app, srs_du::nru_appconfig& f1u_p void srsran::configure_cli11_with_du_appconfig_schema(CLI::App& app, du_appconfig& du_cfg) { - add_option(app, "--du_multicell_enabled", du_cfg.du_multicell_enabled, "DU multicell enabled flag") - ->capture_default_str(); - // Loggers section. configure_cli11_with_logger_appconfig_schema(app, du_cfg.log_cfg); diff --git a/apps/du/du_appconfig_translators.cpp b/apps/du/du_appconfig_translators.cpp index 448c3b601b..9b6c4c32cf 100644 --- a/apps/du/du_appconfig_translators.cpp +++ b/apps/du/du_appconfig_translators.cpp @@ -31,9 +31,8 @@ void srsran::fill_du_worker_manager_config(worker_manager_config& config, const { srsran_assert(config.du_hi_cfg, "DU high worker config does not exist"); - config.du_hi_cfg->pdu_queue_size = unit_cfg.nru_cfg.pdu_queue_size; - config.du_hi_cfg->is_du_multicell_enabled = unit_cfg.du_multicell_enabled; - config.nof_low_prio_threads = unit_cfg.expert_execution_cfg.threads.non_rt_threads.nof_non_rt_threads; - config.low_prio_task_queue_size = unit_cfg.expert_execution_cfg.threads.non_rt_threads.non_rt_task_queue_size; - config.low_prio_sched_config = unit_cfg.expert_execution_cfg.affinities.low_priority_cpu_cfg; + config.du_hi_cfg->pdu_queue_size = unit_cfg.nru_cfg.pdu_queue_size; + config.nof_low_prio_threads = unit_cfg.expert_execution_cfg.threads.non_rt_threads.nof_non_rt_threads; + config.low_prio_task_queue_size = unit_cfg.expert_execution_cfg.threads.non_rt_threads.non_rt_task_queue_size; + config.low_prio_sched_config = unit_cfg.expert_execution_cfg.affinities.low_priority_cpu_cfg; } diff --git a/apps/examples/ofh/ru_emulator.cpp b/apps/examples/ofh/ru_emulator.cpp index 1285c2dfd4..93da4da590 100644 --- a/apps/examples/ofh/ru_emulator.cpp +++ b/apps/examples/ofh/ru_emulator.cpp @@ -351,7 +351,7 @@ static bool decode_rx_message(rx_message_info& message_info, span // Decode and check the filter index in the byte 26, bits 0-3. auto filter_index = static_cast(packet[22] & 0x0f); if (filter_index != filter_index_type::standard_channel_filter && !is_a_prach_message(filter_index)) { - logger.warning("Packet is corrupt: unknown filter index = {} decoded", filter_index); + logger.warning("Packet is corrupt: unknown filter index = {} decoded", fmt::underlying(filter_index)); return false; } message_info.filter_index = filter_index; @@ -501,7 +501,7 @@ class ru_emulator : public frame_notifier uint64_t malformed = corrupt_counter.calculate_acc_value(); uint64_t dropped = dropped_counter.calculate_acc_value(); - fmt::format_to(buffer, + fmt::format_to(std::back_inserter(buffer), "| {:%H:%M:%S} | {:^3} | {:^11} | {:^11} | {:^11} | {:^11} | {:^15} | {:^13} | {:^13} | {:^13} | " "{:^15} | {:^14} | {:^14} | {:^14} | {:^15} | {:^15} | {:^11} | {:^11} | {:^11} |\n", current_time, @@ -597,7 +597,10 @@ class ru_emulator : public frame_notifier { fmt::memory_buffer stats_format_buf; for (unsigned i = 0, e = eaxc.size(); i != e; ++i) { - fmt::format_to(stats_format_buf, "{}{}", seq_id_checker.calculate_statistics(eaxc[i]), (i == e - 1) ? "" : "/"); + fmt::format_to(std::back_inserter(stats_format_buf), + "{}{}", + seq_id_checker.calculate_statistics(eaxc[i]), + (i == e - 1) ? "" : "/"); } return to_string(stats_format_buf); } diff --git a/apps/examples/phy/radio_ssb.cpp b/apps/examples/phy/radio_ssb.cpp index 7a9a507e7a..86e1205f57 100644 --- a/apps/examples/phy/radio_ssb.cpp +++ b/apps/examples/phy/radio_ssb.cpp @@ -76,7 +76,7 @@ static srslog::basic_levels log_level = srslog::basic_levels::warning; /// Program parameters. static subcarrier_spacing scs = subcarrier_spacing::kHz15; static unsigned max_processing_delay_slots = 4; -static cyclic_prefix cp = cyclic_prefix::NORMAL; +static cyclic_prefix cy_prefix = cyclic_prefix::NORMAL; static double dl_center_freq = 3489.42e6; static double ssb_center_freq = 3488.16e6; static double tx_gain = 60.0; @@ -217,7 +217,7 @@ static const auto profiles = to_array({ // parallel execution. for (unsigned channel_id = 0; channel_id != nof_ports * nof_sectors; ++channel_id) { fmt::memory_buffer buffer; - fmt::format_to(buffer, "inproc://{}#{}", getpid(), channel_id); + fmt::format_to(std::back_inserter(buffer), "inproc://{}#{}", getpid(), channel_id); tx_channel_args.emplace_back(to_string(buffer)); rx_channel_args.emplace_back(to_string(buffer)); } @@ -250,7 +250,7 @@ static const auto profiles = to_array({ // parallel execution. for (unsigned channel_id = 0; channel_id != nof_ports * nof_sectors; ++channel_id) { fmt::memory_buffer buffer; - fmt::format_to(buffer, "inproc://{}#{}", getpid(), channel_id); + fmt::format_to(std::back_inserter(buffer), "inproc://{}#{}", getpid(), channel_id); tx_channel_args.emplace_back(to_string(buffer)); rx_channel_args.emplace_back(to_string(buffer)); } @@ -309,7 +309,7 @@ static void usage(std::string_view prog) fmt::print("\t-T Set thread profile (single, dual, quad). [Default {}]\n", thread_profile_name); fmt::print("\t-C Set clock source (internal, external, gpsdo). [Default {}]\n", clock_source); fmt::print("\t-S Set sync source (internal, external, gpsdo). [Default {}]\n", sync_source); - fmt::print("\t-v Logging level. [Default {}]\n", log_level); + fmt::print("\t-v Logging level. [Default {}]\n", fmt::underlying(log_level)); fmt::print("\t-c Enable amplitude clipping. [Default {}]\n", enable_clipping); fmt::print("\t-b Baseband gain back-off prior to clipping (in dB). [Default {}]\n", baseband_backoff_dB); fmt::print("\t-d Fill the resource grid with random data [Default {}]\n", enable_random_data); @@ -475,7 +475,7 @@ lower_phy_configuration create_lower_phy_configuration(task_executor* phy_config.time_alignment_calibration = 0; phy_config.system_time_throttling = 0.0F; phy_config.ta_offset = n_ta_offset::n0; - phy_config.cp = cp; + phy_config.cp = cy_prefix; phy_config.dft_window_offset = 0.5F; phy_config.bb_gateway = &bb_gateway; phy_config.rx_symbol_notifier = rx_symbol_notifier; @@ -530,9 +530,9 @@ int main(int argc, char** argv) // Make sure parameters are valid. report_fatal_error_if_not( srate.is_valid(scs), "Sampling rate ({}) must be multiple of {}kHz.", srate, scs_to_khz(scs)); - report_fatal_error_if_not(cp.is_valid(scs, srate.get_dft_size(scs)), + report_fatal_error_if_not(cy_prefix.is_valid(scs, srate.get_dft_size(scs)), "The cyclic prefix ({}) numerology ({}) and sampling rate ({}) combination is invalid .", - cp.to_string(), + cy_prefix.to_string(), to_numerology_value(scs), srate); diff --git a/apps/gnb/gnb.cpp b/apps/gnb/gnb.cpp index f64b097332..7f8180b184 100644 --- a/apps/gnb/gnb.cpp +++ b/apps/gnb/gnb.cpp @@ -87,7 +87,7 @@ static constexpr unsigned MAX_CONFIG_FILES = 10; static void populate_cli11_generic_args(CLI::App& app) { fmt::memory_buffer buffer; - format_to(buffer, "srsRAN 5G gNB version {} ({})", get_version(), get_build_hash()); + format_to(std::back_inserter(buffer), "srsRAN 5G gNB version {} ({})", get_version(), get_build_hash()); app.set_version_flag("-v,--version", srsran::to_c_str(buffer)); app.set_config("-c,", config_file, "Read config from file", false)->expected(1, MAX_CONFIG_FILES); } @@ -180,10 +180,11 @@ static void autoderive_slicing_args(du_high_unit_config& du_hi_cfg, cu_cp_unit_c static void autoderive_cu_up_parameters_after_parsing(cu_up_unit_config& cu_up_cfg, const cu_cp_unit_config& cu_cp_cfg) { // If no UPF is configured, we set the UPF configuration from the CU-CP AMF configuration. - if (cu_up_cfg.upf_cfg.bind_addr == "auto") { - cu_up_cfg.upf_cfg.bind_addr = cu_cp_cfg.amf_config.amf.bind_addr; + if (cu_up_cfg.ngu_cfg.ngu_socket_cfg.empty()) { + cu_up_unit_ngu_socket_config sock_cfg; + sock_cfg.bind_addr = cu_cp_cfg.amf_config.amf.bind_addr; + cu_up_cfg.ngu_cfg.ngu_socket_cfg.push_back(sock_cfg); } - cu_up_cfg.upf_cfg.no_core = cu_cp_cfg.amf_config.no_core; } int main(int argc, char** argv) @@ -236,6 +237,7 @@ int main(int argc, char** argv) } o_cu_cp_app_unit->on_configuration_parameters_autoderivation(app); + o_cu_up_app_unit->on_configuration_parameters_autoderivation(app); autoderive_cu_up_parameters_after_parsing(o_cu_up_app_unit->get_o_cu_up_unit_config().cu_up_cfg, o_cu_cp_app_unit->get_o_cu_cp_unit_config().cucp_cfg); }); @@ -368,28 +370,35 @@ int main(int argc, char** argv) std::vector metrics_configs; // Instantiate E2AP client gateways. - std::unique_ptr e2_gw_du = create_e2_gateway_client(generate_e2_client_gateway_config( - o_du_app_unit->get_o_du_high_unit_config().e2_cfg.base_cfg, *epoll_broker, *du_pcaps.e2ap, E2_DU_PPID)); + std::unique_ptr e2_gw_du = create_e2_gateway_client( + generate_e2_client_gateway_config(o_du_app_unit->get_o_du_high_unit_config().e2_cfg.base_cfg, + *epoll_broker, + *workers.non_rt_hi_prio_exec, + *du_pcaps.e2ap, + E2_DU_PPID)); std::unique_ptr e2_gw_cu_cp = create_e2_gateway_client( generate_e2_client_gateway_config(o_cu_cp_app_unit->get_o_cu_cp_unit_config().e2_cfg.base_config, *epoll_broker, + *workers.non_rt_hi_prio_exec, *cu_cp_dlt_pcaps.e2ap, E2_CP_PPID)); std::unique_ptr e2_gw_cu_up = create_e2_gateway_client( generate_e2_client_gateway_config(o_cu_up_app_unit->get_o_cu_up_unit_config().e2_cfg.base_config, *epoll_broker, + *workers.non_rt_hi_prio_exec, *cu_up_dlt_pcaps.e2ap, E2_UP_PPID)); // Create O-CU-CP dependencies. o_cu_cp_unit_dependencies o_cucp_deps; - o_cucp_deps.cu_cp_executor = workers.cu_cp_exec; - o_cucp_deps.cu_cp_e2_exec = workers.cu_e2_exec; - o_cucp_deps.timers = cu_timers; - o_cucp_deps.ngap_pcap = cu_cp_dlt_pcaps.ngap.get(); - o_cucp_deps.broker = epoll_broker.get(); - o_cucp_deps.metrics_notifier = &metrics_notifier_forwarder; - o_cucp_deps.e2_gw = e2_gw_cu_cp.get(); + o_cucp_deps.cu_cp_executor = workers.cu_cp_exec; + o_cucp_deps.cu_cp_n2_rx_executor = workers.non_rt_hi_prio_exec; + o_cucp_deps.cu_cp_e2_exec = workers.cu_e2_exec; + o_cucp_deps.timers = cu_timers; + o_cucp_deps.ngap_pcap = cu_cp_dlt_pcaps.ngap.get(); + o_cucp_deps.broker = epoll_broker.get(); + o_cucp_deps.metrics_notifier = &metrics_notifier_forwarder; + o_cucp_deps.e2_gw = e2_gw_cu_cp.get(); // create O-CU-CP. auto o_cucp_unit = o_cu_cp_app_unit->create_o_cu_cp(o_cucp_deps); @@ -424,7 +433,7 @@ int main(int argc, char** argv) odu_dependencies.json_sink = &json_sink; odu_dependencies.metrics_notifier = &metrics_notifier_forwarder; - auto du_inst_and_cmds = o_du_app_unit->create_flexible_o_du_unit(odu_dependencies, gnb_cfg.du_multicell_enabled); + auto du_inst_and_cmds = o_du_app_unit->create_flexible_o_du_unit(odu_dependencies); srs_du::du& du_inst = *du_inst_and_cmds.unit; @@ -445,7 +454,7 @@ int main(int argc, char** argv) commands.push_back(std::move(cmd)); } - app_services::stdin_command_dispatcher command_parser(*epoll_broker, commands); + app_services::stdin_command_dispatcher command_parser(*epoll_broker, *workers.non_rt_low_prio_exec, commands); // Connect E1AP to O-CU-CP. e1_gw->attach_cu_cp(o_cucp_obj.get_cu_cp().get_e1_handler()); diff --git a/apps/gnb/gnb_appconfig.h b/apps/gnb/gnb_appconfig.h index 68d5ada4b0..9346eb5e2f 100644 --- a/apps/gnb/gnb_appconfig.h +++ b/apps/gnb/gnb_appconfig.h @@ -47,8 +47,6 @@ struct metrics_appconfig { struct gnb_appconfig { /// Default constructor to update the log filename. gnb_appconfig() { log_cfg.filename = "/tmp/gnb.log"; } - /// DU multicell flag. - bool du_multicell_enabled = false; /// Loggers configuration. logger_appconfig log_cfg; /// Metrics configuration. diff --git a/apps/gnb/gnb_appconfig_cli11_schema.cpp b/apps/gnb/gnb_appconfig_cli11_schema.cpp index ffc175b03e..b23f398a48 100644 --- a/apps/gnb/gnb_appconfig_cli11_schema.cpp +++ b/apps/gnb/gnb_appconfig_cli11_schema.cpp @@ -55,9 +55,6 @@ void srsran::configure_cli11_with_gnb_appconfig_schema(CLI::App& app, gnb_appcon ->check(CLI::Range(22, 32)); add_option(app, "--ran_node_name", gnb_cfg.ran_node_name, "RAN node name")->capture_default_str(); - add_option(app, "--du_multicell_enabled", gnb_parsed_cfg.du_multicell_enabled, "DU multicell enabled flag") - ->capture_default_str(); - // Loggers section. configure_cli11_with_logger_appconfig_schema(app, gnb_cfg.log_cfg); diff --git a/apps/gnb/gnb_appconfig_translators.cpp b/apps/gnb/gnb_appconfig_translators.cpp index c6850578e9..258965529a 100644 --- a/apps/gnb/gnb_appconfig_translators.cpp +++ b/apps/gnb/gnb_appconfig_translators.cpp @@ -32,9 +32,8 @@ void srsran::fill_gnb_worker_manager_config(worker_manager_config& config, const srsran_assert(config.cu_up_cfg, "CU-UP worker config does not exist"); srsran_assert(config.du_hi_cfg, "DU high worker config does not exist"); - config.du_hi_cfg->pdu_queue_size = config.cu_up_cfg->gtpu_queue_size; - config.du_hi_cfg->is_du_multicell_enabled = unit_cfg.du_multicell_enabled; - config.nof_low_prio_threads = unit_cfg.expert_execution_cfg.threads.non_rt_threads.nof_non_rt_threads; - config.low_prio_task_queue_size = unit_cfg.expert_execution_cfg.threads.non_rt_threads.non_rt_task_queue_size; - config.low_prio_sched_config = unit_cfg.expert_execution_cfg.affinities.low_priority_cpu_cfg; + config.du_hi_cfg->pdu_queue_size = config.cu_up_cfg->gtpu_queue_size; + config.nof_low_prio_threads = unit_cfg.expert_execution_cfg.threads.non_rt_threads.nof_non_rt_threads; + config.low_prio_task_queue_size = unit_cfg.expert_execution_cfg.threads.non_rt_threads.non_rt_task_queue_size; + config.low_prio_sched_config = unit_cfg.expert_execution_cfg.affinities.low_priority_cpu_cfg; } diff --git a/apps/gnb/gnb_appconfig_validators.cpp b/apps/gnb/gnb_appconfig_validators.cpp index d8b709077f..5e89003338 100644 --- a/apps/gnb/gnb_appconfig_validators.cpp +++ b/apps/gnb/gnb_appconfig_validators.cpp @@ -22,6 +22,7 @@ #include "gnb_appconfig_validators.h" #include "apps/services/logger/logger_appconfig_validator.h" +#include "apps/services/worker_manager/worker_manager_appconfig_validator.h" #include "apps/units/flexible_o_du/o_du_high/du_high/du_high_config.h" #include "apps/units/o_cu_cp/cu_cp/cu_cp_unit_config.h" @@ -49,6 +50,10 @@ bool srsran::validate_appconfig(const gnb_appconfig& config) return false; } + if (!validate_expert_execution_appconfig(config.expert_execution_cfg)) { + return false; + } + if (!validate_hal_config(config.hal_config)) { return false; } diff --git a/apps/services/CMakeLists.txt b/apps/services/CMakeLists.txt index 0b48f17cee..ab7f806c0c 100644 --- a/apps/services/CMakeLists.txt +++ b/apps/services/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(buffer_pool) add_subdirectory(e2) add_subdirectory(hal) add_subdirectory(logger) +add_subdirectory(network) add_subdirectory(worker_manager) set(SOURCES @@ -35,6 +36,7 @@ target_include_directories(srsran_app_services PRIVATE ${CMAKE_SOURCE_DIR}) target_link_libraries(srsran_app_services srsran_buffer_pool_app_service srsran_e2_app_service + srsran_network_app_service srsran_hal_app_service srsran_logger_app_service srsran_worker_manager_app_service) diff --git a/apps/services/network/CMakeLists.txt b/apps/services/network/CMakeLists.txt new file mode 100644 index 0000000000..b3d7c200e7 --- /dev/null +++ b/apps/services/network/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright 2021-2024 Software Radio Systems Limited +# +# This file is part of srsRAN +# +# srsRAN is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# srsRAN is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# A copy of the GNU Affero General Public License can be found in +# the LICENSE file in the top-level directory of this distribution +# and at http://www.gnu.org/licenses/. +# + +set(SOURCES udp_cli11_schema.cpp) + +add_library(srsran_network_app_service STATIC ${SOURCES}) +target_include_directories(srsran_network_app_service PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/apps/services/network/udp_cli11_schema.cpp b/apps/services/network/udp_cli11_schema.cpp new file mode 100644 index 0000000000..6077338061 --- /dev/null +++ b/apps/services/network/udp_cli11_schema.cpp @@ -0,0 +1,44 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "udp_cli11_schema.h" +#include "srsran/support/cli11_utils.h" + +using namespace srsran; + +static void configure_cli11_udp_args(CLI::App& app, udp_appconfig& udp_params) +{ + add_option(app, "--max_rx_msgs", udp_params.rx_max_msgs, "Maximum amount of messages RX in a single syscall") + ->capture_default_str(); + add_option( + app, "--pool_threshold", udp_params.pool_threshold, "Pool accupancy threshold after which packets are dropped") + ->capture_default_str(); + add_option(app, "--dscp", udp_params.dscp, "Differentiated Services Code Point value.") + ->capture_default_str() + ->check(CLI::Range(0, 63)); +} + +void srsran::configure_cli11_with_udp_config_schema(CLI::App& app, udp_appconfig& config) +{ + CLI::App* udp_subcmd = add_subcommand(app, "udp", "UDP parameters")->configurable(); + configure_cli11_udp_args(*udp_subcmd, config); +} diff --git a/apps/services/network/udp_cli11_schema.h b/apps/services/network/udp_cli11_schema.h new file mode 100644 index 0000000000..16e4df322f --- /dev/null +++ b/apps/services/network/udp_cli11_schema.h @@ -0,0 +1,46 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +#include "CLI/CLI11.hpp" +#include + +namespace srsran { + +/// UDP specific configuration of an UDP gateway. +struct udp_appconfig { + /// Maximum amount of messages RX in a single syscall. + unsigned rx_max_msgs = 256; + /// Pool accupancy threshold after which packets are dropped. + float pool_threshold = 0.9; + /// Differentiated Services Code Point value. + std::optional dscp; +}; + +/// \brief Configures the given CLI11 application with the UDP application configuration schema. +/// +/// \param[out] app CLI11 application to configure. +/// \param[out] config UDP configuration that stores the parameters. +void configure_cli11_with_udp_config_schema(CLI::App& app, udp_appconfig& config); + +} // namespace srsran diff --git a/apps/services/stdin_command_dispatcher.cpp b/apps/services/stdin_command_dispatcher.cpp index a92bd78c9d..82442c2af7 100644 --- a/apps/services/stdin_command_dispatcher.cpp +++ b/apps/services/stdin_command_dispatcher.cpp @@ -61,7 +61,8 @@ class sleep_app_command : public application_command // See interface for documentation. void execute(span args) override - { // Verify that the number of arguments is valid. + { + // Verify that the number of arguments is valid. if (args.size() != 1) { fmt::print("Invalid sleep command syntax. Usage: sleep \n"); return; @@ -89,6 +90,7 @@ class sleep_app_command : public application_command } // namespace stdin_command_dispatcher::stdin_command_dispatcher(io_broker& io_broker, + task_executor& executor, span> commands_) : logger(srslog::fetch_basic_logger("APP")) { @@ -111,13 +113,13 @@ stdin_command_dispatcher::stdin_command_dispatcher(io_broker& cmd = std::move(app_cmd); } - // Set STDIN file descripter into non-blocking mode + // Set STDIN file descriptor into non-blocking mode. int flags = ::fcntl(STDIN_FILENO, F_GETFL, 0); if (::fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) { logger.error("Couldn't configure fd to non-blocking"); } - stdin_handle = io_broker.register_fd(STDIN_FILENO, [this]() { parse_stdin(STDIN_FILENO); }); + stdin_handle = io_broker.register_fd(unique_fd(STDIN_FILENO, false), executor, [this]() { parse_stdin(); }); if (!stdin_handle.registered()) { logger.error("Couldn't register stdin handler"); } @@ -128,7 +130,7 @@ static void string_parse_list(const std::string& input, char delimiter, std::vec { std::stringstream ss(input); - // Removes all possible elements of the list + // Removes all possible elements of the list. commands.clear(); while (ss.good()) { @@ -141,18 +143,17 @@ static void string_parse_list(const std::string& input, char delimiter, std::vec } } -void stdin_command_dispatcher::parse_stdin(int file_descriptor) +void stdin_command_dispatcher::parse_stdin() { - static constexpr unsigned read_chunk = 256; + static constexpr size_t read_chunk = 256; + unsigned total_bytes_read = 0; std::array buffer; - int bytes_read = 0; - int total_bytes_read = 0; logger.debug("Stdin has data to read"); do { - // Read from stdin until EWOULDBLOCK is set - bytes_read = ::read(file_descriptor, &buffer[total_bytes_read], read_chunk); + // Read from stdin until EWOULDBLOCK is set. + int bytes_read = ::read(STDIN_FILENO, &buffer[total_bytes_read], read_chunk); if (bytes_read < 0) { if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) { break; @@ -171,7 +172,7 @@ void stdin_command_dispatcher::parse_stdin(int file_descriptor) logger.debug("read {} B from stdin", total_bytes_read); - // Convert buffer to string + // Convert buffer to string. std::string input_line(buffer.begin(), buffer.begin() + total_bytes_read); std::vector cmd_list; @@ -196,7 +197,7 @@ void stdin_command_dispatcher::handle_command(const std::string& command) srsran_assert(!arg_list.empty(), "Parsing empty command argument list"); - if (const auto& cmd = commands.find(arg_list.front()); cmd != commands.end()) { + if (auto cmd = commands.find(arg_list.front()); cmd != commands.end()) { cmd->second->execute(span(arg_list).last(arg_list.size() - 1)); } else { print_help(); diff --git a/apps/services/stdin_command_dispatcher.h b/apps/services/stdin_command_dispatcher.h index c78dbf4fe9..1a1d81a944 100644 --- a/apps/services/stdin_command_dispatcher.h +++ b/apps/services/stdin_command_dispatcher.h @@ -39,11 +39,13 @@ namespace app_services { class stdin_command_dispatcher { public: - stdin_command_dispatcher(io_broker& io_broker, span> commands_); + stdin_command_dispatcher(io_broker& io_broker, + task_executor& executor, + span> commands_); private: - /// Parses STDIN with the given file decriptor. - void parse_stdin(int file_descriptor); + /// Parses any contents in the STDIN file descriptor. + void parse_stdin(); /// Handles the given command; void handle_command(const std::string& command); diff --git a/apps/services/worker_manager/CMakeLists.txt b/apps/services/worker_manager/CMakeLists.txt index 13450a1226..337923688f 100644 --- a/apps/services/worker_manager/CMakeLists.txt +++ b/apps/services/worker_manager/CMakeLists.txt @@ -20,7 +20,8 @@ set(SOURCES worker_manager.cpp - worker_manager_cli11_schema.cpp) + worker_manager_cli11_schema.cpp + worker_manager_appconfig_validator.cpp) add_library(srsran_cpu_affinities_helper STATIC cli11_cpu_affinities_parser_helper.cpp) diff --git a/apps/services/worker_manager/cli11_cpu_affinities_parser_helper.cpp b/apps/services/worker_manager/cli11_cpu_affinities_parser_helper.cpp index b2d740b4f1..1989ad8cf0 100644 --- a/apps/services/worker_manager/cli11_cpu_affinities_parser_helper.cpp +++ b/apps/services/worker_manager/cli11_cpu_affinities_parser_helper.cpp @@ -41,9 +41,10 @@ static expected parse_int(const std::string& value) static error_type is_valid_cpu_index(unsigned cpu_idx) { - std::string error_message = fmt::format("Invalid CPU core selected '{}'. Valid CPU ids: {}", - cpu_idx, - os_sched_affinity_bitmask::available_cpus().get_cpu_ids()); + std::string error_message = + fmt::format("Invalid CPU core selected '{}'. Valid CPU ids: {}", + cpu_idx, + span(os_sched_affinity_bitmask::available_cpus().get_cpu_ids())); os_sched_affinity_bitmask one_cpu_mask; if (cpu_idx >= one_cpu_mask.size()) { diff --git a/apps/services/worker_manager/worker_manager.cpp b/apps/services/worker_manager/worker_manager.cpp index 63e7995b0d..025c830c24 100644 --- a/apps/services/worker_manager/worker_manager.cpp +++ b/apps/services/worker_manager/worker_manager.cpp @@ -256,50 +256,26 @@ void worker_manager::create_du_executors(const worker_manager_config::du_high_co // Instantiate DU-high executor mapper. srs_du::du_high_executor_config cfg; - if (du_hi.is_du_multicell_enabled) { - // DU multicell enabled. Create one executor mapper. - du_high_executors.resize(1); - auto& du_item = du_high_executors[0]; - srs_du::du_high_executor_config::dedicated_cell_worker_list cell_workers; - for (unsigned i = 0; i != du_hi.nof_cells; ++i) { - const std::string cell_id_str = std::to_string(i); - cell_workers.push_back({*exec_map.at("slot_exec#" + cell_id_str), *exec_map.at("cell_exec#" + cell_id_str)}); - } - cfg.cell_executors.emplace(std::move(cell_workers)); - cfg.ue_executors.policy = srs_du::du_high_executor_config::ue_executor_config::map_policy::per_cell; - cfg.ue_executors.max_nof_strands = 1; - cfg.ue_executors.ctrl_queue_size = task_worker_queue_size; - cfg.ue_executors.pdu_queue_size = du_hi.pdu_queue_size; - cfg.ue_executors.pool_executor = exec_map.at("low_prio_exec"); - cfg.ctrl_executors.task_queue_size = task_worker_queue_size; - cfg.ctrl_executors.pool_executor = exec_map.at("high_prio_exec"); - cfg.is_rt_mode_enabled = du_hi.is_rt_mode_enabled; - cfg.trace_exec_tasks = false; - - du_item.du_high_exec_mapper = srs_du::create_du_high_executor_mapper(cfg); - } else { - // DU single cell enabled. Create one executor mapper per DU/cell. - du_high_executors.resize(du_hi.nof_cells); - for (unsigned i = 0; i != du_hi.nof_cells; ++i) { - auto& du_item = du_high_executors[i]; - const std::string cell_id_str = std::to_string(i); - - // DU-high executor mapper creation. - cfg.cell_executors = srs_du::du_high_executor_config::dedicated_cell_worker_list{ - {*exec_map.at("slot_exec#" + cell_id_str), *exec_map.at("cell_exec#" + cell_id_str)}}; - cfg.ue_executors.policy = srs_du::du_high_executor_config::ue_executor_config::map_policy::per_cell; - cfg.ue_executors.max_nof_strands = 1; - cfg.ue_executors.ctrl_queue_size = task_worker_queue_size; - cfg.ue_executors.pdu_queue_size = du_hi.pdu_queue_size; - cfg.ue_executors.pool_executor = exec_map.at("low_prio_exec"); - cfg.ctrl_executors.task_queue_size = task_worker_queue_size; - cfg.ctrl_executors.pool_executor = exec_map.at("high_prio_exec"); - cfg.is_rt_mode_enabled = du_hi.is_rt_mode_enabled; - cfg.trace_exec_tasks = false; - - du_item.du_high_exec_mapper = srs_du::create_du_high_executor_mapper(cfg); - } + // Create one executor mapper as one DU supports multiple cells. + du_high_executors.resize(1); + auto& du_item = du_high_executors[0]; + srs_du::du_high_executor_config::dedicated_cell_worker_list cell_workers; + for (unsigned i = 0; i != du_hi.nof_cells; ++i) { + const std::string cell_id_str = std::to_string(i); + cell_workers.push_back({*exec_map.at("slot_exec#" + cell_id_str), *exec_map.at("cell_exec#" + cell_id_str)}); } + cfg.cell_executors.emplace(std::move(cell_workers)); + cfg.ue_executors.policy = srs_du::du_high_executor_config::ue_executor_config::map_policy::per_cell; + cfg.ue_executors.max_nof_strands = 1; + cfg.ue_executors.ctrl_queue_size = task_worker_queue_size; + cfg.ue_executors.pdu_queue_size = du_hi.pdu_queue_size; + cfg.ue_executors.pool_executor = exec_map.at("low_prio_exec"); + cfg.ctrl_executors.task_queue_size = task_worker_queue_size; + cfg.ctrl_executors.pool_executor = exec_map.at("high_prio_exec"); + cfg.is_rt_mode_enabled = du_hi.is_rt_mode_enabled; + cfg.trace_exec_tasks = false; + + du_item.du_high_exec_mapper = srs_du::create_du_high_executor_mapper(cfg); if (du_low) { create_du_low_executors(du_low.value().is_blocking_mode_active, @@ -358,6 +334,9 @@ void worker_manager::create_low_prio_executors(const worker_manager_config& work if (not exec_mng.add_execution_context(create_execution_context(non_rt_pool))) { report_fatal_error("Failed to instantiate {} execution context", non_rt_pool.name); } + + non_rt_low_prio_exec = exec_mng.executors().at("low_prio_exec"); + non_rt_hi_prio_exec = exec_mng.executors().at("high_prio_exec"); } void worker_manager::associate_low_prio_executors(const worker_manager_config& config) diff --git a/apps/services/worker_manager/worker_manager.h b/apps/services/worker_manager/worker_manager.h index efeccd4b9f..8ab7511c58 100644 --- a/apps/services/worker_manager/worker_manager.h +++ b/apps/services/worker_manager/worker_manager.h @@ -67,8 +67,10 @@ struct worker_manager : public worker_manager_executor_getter { std::vector fapi_exec; std::vector ru_dl_exec; std::vector ru_rx_exec; - task_executor* cu_e2_exec = nullptr; - task_executor* metrics_hub_exec = nullptr; + task_executor* cu_e2_exec = nullptr; + task_executor* metrics_hub_exec = nullptr; + task_executor* non_rt_low_prio_exec = nullptr; + task_executor* non_rt_hi_prio_exec = nullptr; std::unique_ptr cu_up_exec_mapper; diff --git a/apps/services/worker_manager/worker_manager_appconfig_validator.cpp b/apps/services/worker_manager/worker_manager_appconfig_validator.cpp new file mode 100644 index 0000000000..f125c5fa5f --- /dev/null +++ b/apps/services/worker_manager/worker_manager_appconfig_validator.cpp @@ -0,0 +1,41 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#include "worker_manager_appconfig_validator.h" +#include "worker_manager_appconfig.h" + +using namespace srsran; + +bool srsran::validate_expert_execution_appconfig(const expert_execution_appconfig& config) +{ + // Ensure the number of non-real time threads does not exceed the number of CPU cores + auto& cpu_desc = cpu_architecture_info::get(); + uint32_t nof_cores = cpu_desc.get_host_nof_available_cpus(); + if (config.threads.non_rt_threads.nof_non_rt_threads > nof_cores) { + fmt::print("Invalid expert execution config: nof_non_rt_threads={} must not exceed nof_cores={}\n", + config.threads.non_rt_threads.nof_non_rt_threads, + nof_cores); + return false; + } + + return true; +} diff --git a/apps/services/worker_manager/worker_manager_appconfig_validator.h b/apps/services/worker_manager/worker_manager_appconfig_validator.h new file mode 100644 index 0000000000..6becc2fc71 --- /dev/null +++ b/apps/services/worker_manager/worker_manager_appconfig_validator.h @@ -0,0 +1,32 @@ +/* + * + * Copyright 2021-2024 Software Radio Systems Limited + * + * This file is part of srsRAN. + * + * srsRAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * srsRAN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * A copy of the GNU Affero General Public License can be found in + * the LICENSE file in the top-level directory of this distribution + * and at http://www.gnu.org/licenses/. + * + */ + +#pragma once + +namespace srsran { + +struct expert_execution_appconfig; + +/// Validates the given expert configuration of the application. Returns true on success, false otherwise. +bool validate_expert_execution_appconfig(const expert_execution_appconfig& config); + +} // namespace srsran diff --git a/apps/services/worker_manager/worker_manager_config.h b/apps/services/worker_manager/worker_manager_config.h index e3639fe48a..04a822e573 100644 --- a/apps/services/worker_manager/worker_manager_config.h +++ b/apps/services/worker_manager/worker_manager_config.h @@ -83,8 +83,6 @@ struct worker_manager_config { unsigned nof_cells; /// Real-time mode enabled flag. bool is_rt_mode_enabled; - /// Multicell DU enabled flag. - bool is_du_multicell_enabled; }; // CU-UP worker configuration diff --git a/apps/units/flexible_o_du/flexible_o_du_application_unit.h b/apps/units/flexible_o_du/flexible_o_du_application_unit.h index f0c9161e0f..82b8678e03 100644 --- a/apps/units/flexible_o_du/flexible_o_du_application_unit.h +++ b/apps/units/flexible_o_du/flexible_o_du_application_unit.h @@ -37,8 +37,7 @@ class flexible_o_du_application_unit : public application_unit virtual ~flexible_o_du_application_unit() = default; /// Creates a flexible O-RAN DU using the given dependencies. - virtual o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, - bool use_multicell = false) = 0; + virtual o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) = 0; /// Returns the O-RAN DU high unit configuration of this flexible DU. virtual o_du_high_unit_config& get_o_du_high_unit_config() = 0; diff --git a/apps/units/flexible_o_du/flexible_o_du_commands.h b/apps/units/flexible_o_du/flexible_o_du_commands.h index fd9bc11eb9..dc7445c0e7 100644 --- a/apps/units/flexible_o_du/flexible_o_du_commands.h +++ b/apps/units/flexible_o_du/flexible_o_du_commands.h @@ -27,6 +27,7 @@ #include "srsran/adt/expected.h" #include "srsran/adt/to_array.h" #include "srsran/ru/ru_controller.h" +#include "srsran/srslog/srslog.h" namespace srsran { diff --git a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config.h b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config.h index 766a5bd1fc..b84fbe0f1a 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config.h +++ b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config.h @@ -43,6 +43,7 @@ #include "srsran/ran/sib/system_info_config.h" #include "srsran/ran/slot_pdu_capacity_constants.h" #include "srsran/ran/subcarrier_spacing.h" +#include "srsran/ran/tac.h" #include "srsran/scheduler/config/scheduler_expert_config.h" #include "srsran/srslog/srslog.h" #include @@ -81,7 +82,7 @@ struct du_high_unit_ta_sched_expert_config { /// \remark T_A is defined in TS 38.213, clause 4.2. int ta_cmd_offset_threshold = 1; /// Timing Advance target in units of TA. - int ta_target = 0; + float ta_target = 1.0F; /// UL SINR threshold (in dB) above which reported N_TA update measurement is considered valid. float ta_update_measurement_ul_sinr_threshold = 0.0F; }; @@ -146,7 +147,7 @@ struct du_high_unit_pdsch_config { /// Redundancy version sequence to use. Each element can have one of the following values: {0, 1, 2, 3}. std::vector rv_sequence = {0, 2, 3, 1}; /// MCS table to use for PDSCH - pdsch_mcs_table mcs_table = pdsch_mcs_table::qam64; + pdsch_mcs_table mcs_table = pdsch_mcs_table::qam256; /// Minimum number of RBs for resource allocation of UE PDSCHs. unsigned min_rb_size = 1; /// Maximum number of RBs for resource allocation of UE PDSCHs. @@ -196,7 +197,7 @@ struct du_high_unit_pusch_config { /// Maximum rank. Limits the number of layers for PUSCH transmissions. unsigned max_rank = 4; /// MCS table to use for PUSCH - pusch_mcs_table mcs_table = pusch_mcs_table::qam64; + pusch_mcs_table mcs_table = pusch_mcs_table::qam256; /// \c msg3-DeltaPreamble, TS 38.331. Values: {-1,...,6}. int msg3_delta_preamble = 6; /// \c p0-NominalWithGrant, TS 38.331. Value in dBm. Only even values allowed within {-202,...,24}. @@ -248,11 +249,15 @@ struct du_high_unit_pusch_config { /// End RB for resource allocation of UE PUSCHs. unsigned end_rb = MAX_NOF_PRBS; - /// Target PUSCH SINR to be achieved with close-loop power control, in dB. + /// Enable closed-loop PUSCH power control. + bool enable_closed_loop_pw_control = false; + /// Target PUSCH SINR to be achieved with close-loop power control, in dB. Only relevant if \c + /// enable_closed_loop_pw_control is set to true. float target_pusch_sinr{10.0f}; /// Path-loss at which the Target PUSCH SINR is expected to be achieved, in dB. /// This is used to compute the path loss compensation for PUSCH fractional power control. The value must be positive. - /// Only relevant if \c path_loss_compensation_factor is set to a value different from 1.0. + /// Only relevant if \c enable_closed_loop_pw_control is set to true and \c path_loss_compensation_factor is set to a + /// value different from 1.0. float path_loss_for_target_pusch_sinr{70.0f}; /// Factor "alpha" for fractional path-loss compensation in PUSCH power control. /// Values: {0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}. @@ -272,6 +277,8 @@ struct du_high_unit_pucch_config { /// \c PUCCH-Config parameters. /// Force Format 0 for the PUCCH resources belonging to PUCCH resource set 0. bool use_format_0 = false; + /// Select the format for the PUCCH resources belonging to PUCCH resource set 1. Values: {2, 3, 4}. + unsigned set1_format = 2; /// Number of PUCCH resources per UE (per PUCCH resource set) for HARQ-ACK reporting. /// Values {3,...,8} if \c use_format_0 is set. Else, Values {1,...,8}. /// \remark We assume the number of PUCCH F0/F1 resources for HARQ-ACK is equal to the equivalent number of Format 2 @@ -296,6 +303,7 @@ struct du_high_unit_pucch_config { /// Set true for PUCCH Format 0 intra-slot frequency hopping. bool f0_intraslot_freq_hopping = false; + /// PUCCH F1 resource parameters. /// \defgroup pucch_f1_params /// \brief PUCCH F1 resource parameters. /// @{ @@ -303,20 +311,60 @@ struct du_high_unit_pucch_config { bool f1_enable_occ = false; /// \brief Number of different Initial Cyclic Shifts that can be used for PUCCH Format 1. /// Values: {1, 2, 3, 4, 6, 12}; 0 corresponds to "no cyclic shift". - unsigned nof_cyclic_shift = 2; + unsigned f1_nof_cyclic_shifts = 2; /// Set true for PUCCH Format 1 intra-slot frequency hopping. bool f1_intraslot_freq_hopping = false; /// @} + /// PUCCH F2 resource parameters. + /// \defgroup pucch_f2_params + /// \brief PUCCH F2 resource parameters. + /// @{ /// Max number of PRBs for PUCCH Format 2. Values {1,...,16}. unsigned f2_max_nof_rbs = 1; - /// \brief Maximum payload in bits that can be carried by PUCCH Format 2. Values {-1,...,11}. - /// Value -1 to unset. If this is set, \ref f2_max_nof_rbs is ignored. - std::optional max_payload_bits; + /// \brief Maximum payload in bits that can be carried by PUCCH Format 2. Values {1,...,11}. + /// If this is set, \ref f2_max_nof_rbs is ignored. + std::optional f2_max_payload_bits; + /// Max code rate for PUCCH Format 2. + max_pucch_code_rate f2_max_code_rate = max_pucch_code_rate::dot_35; /// Set true for PUCCH Format 2 intra-slot frequency hopping. This field is ignored if f2_nof_symbols == 1. bool f2_intraslot_freq_hopping = false; - /// Max code rate. - max_pucch_code_rate max_code_rate = max_pucch_code_rate::dot_35; + /// @} + + /// PUCCH F3 resource parameters. + /// \defgroup pucch_f3_params + /// \brief PUCCH F3 resource parameters. + /// @{ + /// Max number of PRBs for PUCCH Format 3. Values {1,...,16}. + unsigned f3_max_nof_rbs = 1; + /// \brief Maximum payload in bits that can be carried by PUCCH Format 3. Values {1,...,11}. + /// If this is set, \ref f2_max_nof_rbs is ignored. + std::optional f3_max_payload_bits; + /// Max code rate for PUCCH Format 3. + max_pucch_code_rate f3_max_code_rate = max_pucch_code_rate::dot_35; + /// Set true for PUCCH Format 3 intra-slot frequency hopping. + bool f3_intraslot_freq_hopping = false; + /// Set true for PUCCH Format 3 additional DM-RS. + bool f3_additional_dmrs = false; + /// Set true to use pi/2-BPSK as the modulation for PUCCH Format 3. + bool f3_pi2_bpsk = false; + /// @} + + /// PUCCH F4 resource parameters. + /// \defgroup pucch_f4_params + /// \brief PUCCH F4 resource parameters. + /// @{ + /// Max code rate for PUCCH Format 4. + max_pucch_code_rate f4_max_code_rate = max_pucch_code_rate::dot_35; + /// Set true for PUCCH Format 4 intra-slot frequency hopping. + bool f4_intraslot_freq_hopping = false; + /// Set true for PUCCH Format 4 additional DM-RS. + bool f4_additional_dmrs = false; + /// Set true to use pi/2-BPSK as the modulation for PUCCH Format 4. + bool f4_pi2_bpsk = false; + unsigned f4_occ_length = 2; + /// @} + /// Minimum k1 value (distance in slots between PDSCH and HARQ-ACK) that the gNB can use. Values: {1, ..., 7}. /// [Implementation-defined] As min_k1 is used for both common and dedicated PUCCH configuration, and in the UE /// fallback scheduler only allow max k1 = 7, we restrict min_k1 to 7. @@ -626,7 +674,7 @@ struct du_high_unit_base_cell_config { /// Human readable full PLMN (without possible filler digit). std::string plmn = "00101"; /// TAC. - unsigned tac = 7; + tac_t tac = 7; /// \c q-RxLevMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dBm. int q_rx_lev_min = -70; /// \c q-QualMin, part of \c cellSelectionInfo, \c SIB1, TS 38.311, in dB. diff --git a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_cli11_schema.cpp b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_cli11_schema.cpp index 9cdd36c524..13d868a2fe 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_cli11_schema.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_cli11_schema.cpp @@ -59,11 +59,11 @@ static std::function get_vector_default_function(spandefault_str("qam64") + ->default_str("qam256") ->check(CLI::IsMember({"qam64", "qam256"}, CLI::ignore_case)); add_option(app, "--min_rb_size", pdsch_params.min_rb_size, "Minimum RB size for UE PDSCH resource allocation") ->capture_default_str() @@ -584,7 +588,7 @@ static void configure_cli11_ta_scheduler_expert_args(CLI::App& app, du_high_unit ->check(CLI::Range(-1, 31)); add_option(app, "--ta_target", ta_params.ta_target, "Timing Advance target in units of TA") ->capture_default_str() - ->check(CLI::Range(-30, 30)); + ->check(CLI::Range(-30.0, 30.0)); add_option(app, "--ta_update_measurement_ul_sinr_threshold", ta_params.ta_update_measurement_ul_sinr_threshold, @@ -665,12 +669,16 @@ static void configure_cli11_pusch_args(CLI::App& app, du_high_unit_pusch_config& app, "--mcs_table", [&pusch_params](const std::string& value) { - if (value == "qam256") { + if (value == "qam64") { + pusch_params.mcs_table = pusch_mcs_table::qam64; + } else if (value == "qam256") { pusch_params.mcs_table = pusch_mcs_table::qam256; + } else { + report_fatal_error("PUSCH mcs_table={} not in {{qam64,qam256}}.", value); } }, "MCS table to use PUSCH") - ->default_str("qam64") + ->default_str(pusch_mcs_table_to_string(pusch_params.mcs_table)) ->check(CLI::IsMember({"qam64", "qam256"}, CLI::ignore_case)); add_option(app, "--max_rank", @@ -836,6 +844,27 @@ static void configure_cli11_pusch_args(CLI::App& app, du_high_unit_pusch_config& static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& pucch_params) { + // Lambda function to map the argument values to max_pucch_code_rate values. + auto map_max_code_rate = [](max_pucch_code_rate& max_code_rate) { + return [&max_code_rate](const std::string& value) { + if (value == "dot08") { + max_code_rate = max_pucch_code_rate::dot_08; + } else if (value == "dot15") { + max_code_rate = max_pucch_code_rate::dot_15; + } else if (value == "dot25") { + max_code_rate = max_pucch_code_rate::dot_25; + } else if (value == "dot35") { + max_code_rate = max_pucch_code_rate::dot_35; + } else if (value == "dot45") { + max_code_rate = max_pucch_code_rate::dot_45; + } else if (value == "dot60") { + max_code_rate = max_pucch_code_rate::dot_60; + } else if (value == "dot80") { + max_code_rate = max_pucch_code_rate::dot_80; + } + }; + }; + add_option(app, "--p0_nominal", pucch_params.p0_nominal, @@ -864,6 +893,12 @@ static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& ->check(CLI::IsMember({1.0F, 2.0F, 2.5F, 4.0F, 5.0F, 8.0F, 10.0F, 16.0F, 20.0F, 40.0F, 80.0F, 160.0F, 320.0F})); add_option(app, "--use_format_0", pucch_params.use_format_0, "Use Format 0 for PUCCH resources from resource set 0") ->capture_default_str(); + add_option(app, + "--pucch_set1_format", + pucch_params.set1_format, + "Format to use for the resources from resource set 1. Values: {2, 3, 4}. Default: 2") + ->capture_default_str() + ->check(CLI::Range(2, 4)); add_option(app, "--nof_ue_res_harq_per_set", pucch_params.nof_ue_pucch_res_harq_per_set, @@ -873,7 +908,7 @@ static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& add_option(app, "--f0_or_f1_nof_cell_res_sr", pucch_params.nof_cell_sr_resources, - "Number of PUCCH F1 resources available per cell for SR") + "Number of PUCCH F0/F1 resources available per cell for SR") ->capture_default_str() ->check(CLI::Range(1, 100)); add_option(app, @@ -884,7 +919,7 @@ static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& add_option(app, "--f1_enable_occ", pucch_params.f1_enable_occ, "Enable OCC for PUCCH F1")->capture_default_str(); add_option(app, "--f1_nof_cyclic_shifts", - pucch_params.nof_cyclic_shift, + pucch_params.f1_nof_cyclic_shifts, "Number of possible cyclic shifts available for PUCCH F1 resources") ->capture_default_str() ->check(CLI::IsMember({1, 2, 3, 4, 6, 12})); @@ -901,36 +936,21 @@ static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& ->capture_default_str() ->check(CLI::Range(1, 10)); add_option(app, - "--f2_nof_cell_res_csi", + "--f2_or_f3_or_f4_nof_cell_res_csi", pucch_params.nof_cell_csi_resources, - "Number of PUCCH F2 resources available per cell for CSI") + "Number of PUCCH F2/F3/F4 resources available per cell for CSI") ->capture_default_str() ->check(CLI::Range(0, 100)); add_option(app, "--f2_max_nof_rbs", pucch_params.f2_max_nof_rbs, "Max number of RBs for PUCCH F2 resources") ->capture_default_str() ->check(CLI::Range(1, 16)); - add_option(app, "--f2_max_payload", pucch_params.max_payload_bits, "Max number payload bits for PUCCH F2 resources") + add_option( + app, "--f2_max_payload", pucch_params.f2_max_payload_bits, "Max number payload bits for PUCCH F2 resources") ->check(CLI::Range(1, 11)); add_option_function( app, "--f2_max_code_rate", - [&pucch_params](const std::string& value) { - if (value == "dot08") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_08; - } else if (value == "dot15") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_15; - } else if (value == "dot25") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_25; - } else if (value == "dot35") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_35; - } else if (value == "dot45") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_45; - } else if (value == "dot60") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_60; - } else if (value == "dot80") { - pucch_params.max_code_rate = max_pucch_code_rate::dot_80; - } - }, + map_max_code_rate(pucch_params.f2_max_code_rate), "PUCCH F2 max code rate {dot08, dot15, dot25, dot35, dot45, dot60, dot80}. Default: dot35") ->check(CLI::IsMember({"dot08", "dot15", "dot25", "dot35", "dot45", "dot60", "dot80"}, CLI::ignore_case)); add_option(app, @@ -938,6 +958,45 @@ static void configure_cli11_pucch_args(CLI::App& app, du_high_unit_pucch_config& pucch_params.f2_intraslot_freq_hopping, "Enable intra-slot frequency hopping for PUCCH F2") ->capture_default_str(); + add_option(app, "--f3_max_nof_rbs", pucch_params.f3_max_nof_rbs, "Max number of RBs for PUCCH F3 resources") + ->capture_default_str() + ->check(CLI::IsMember({1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16})); + add_option( + app, "--f3_max_payload", pucch_params.f3_max_payload_bits, "Max number payload bits for PUCCH F3 resources") + ->check(CLI::Range(1, 11)); + add_option_function( + app, + "--f3_max_code_rate", + map_max_code_rate(pucch_params.f3_max_code_rate), + "PUCCH F3 max code rate {dot08, dot15, dot25, dot35, dot45, dot60, dot80}. Default: dot35") + ->check(CLI::IsMember({"dot08", "dot15", "dot25", "dot35", "dot45", "dot60", "dot80"}, CLI::ignore_case)); + add_option(app, + "--f3_intraslot_freq_hop", + pucch_params.f3_intraslot_freq_hopping, + "Enable intra-slot frequency hopping for PUCCH F3") + ->capture_default_str(); + add_option(app, "--f3_additional_dmrs", pucch_params.f3_additional_dmrs, "Enable additional DM-RS for PUCCH F3") + ->capture_default_str(); + add_option(app, "--f3_pi2_bpsk", pucch_params.f3_pi2_bpsk, "Enable pi/2-BPSK modulation for PUCCH F3") + ->capture_default_str(); + add_option_function( + app, + "--f4_max_code_rate", + map_max_code_rate(pucch_params.f4_max_code_rate), + "PUCCH F4 max code rate {dot08, dot15, dot25, dot35, dot45, dot60, dot80}. Default: dot35") + ->check(CLI::IsMember({"dot08", "dot15", "dot25", "dot35", "dot45", "dot60", "dot80"}, CLI::ignore_case)); + add_option(app, + "--f4_intraslot_freq_hop", + pucch_params.f4_intraslot_freq_hopping, + "Enable intra-slot frequency hopping for PUCCH F4") + ->capture_default_str(); + add_option(app, "--f4_additional_dmrs", pucch_params.f4_additional_dmrs, "Enable additional DM-RS for PUCCH F4") + ->capture_default_str(); + add_option(app, "--f4_pi2_bpsk", pucch_params.f4_pi2_bpsk, "Enable pi/2-BPSK modulation for PUCCH F4") + ->capture_default_str(); + add_option(app, "--f4_occ_length", pucch_params.f4_occ_length, "OCC length for PUCCH F4") + ->capture_default_str() + ->check(CLI::IsMember({2, 4})); add_option(app, "--min_k1", pucch_params.min_k1, @@ -1341,8 +1400,8 @@ static void configure_cli11_common_cell_args(CLI::App& app, du_high_unit_base_ce configure_cli11_prach_args(*prach_subcmd, cell_params.prach_cfg); // TDD UL DL configuration. - // NOTE: As the cell TDD pattern can be configured in the common cell and not in the cell, use a different variable to - // parse the cell TDD property. If the TDD pattern is present for the cell, update the cell TDD pattern value, + // NOTE: As the cell TDD pattern can be configured in the common cell and not in the cell, use a different variable + // to parse the cell TDD property. If the TDD pattern is present for the cell, update the cell TDD pattern value, // otherwise do nothing (this will cause that the cell TDD pattern value equals than the common cell TDD pattern). // CLI11 needs that the life of the variable last longer than the call of callback function. Therefore, the // cell_tdd_pattern variable needs to be static. diff --git a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.cpp b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.cpp index af635a184b..7c4dc02f64 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.cpp @@ -229,7 +229,8 @@ static void fill_csi_resources(serving_cell_config& out_cell, const du_high_unit out_cell.csi_meas_cfg->csi_report_cfg_list[0].cqi_table = cqi_table_t::table3; break; default: - report_error("Invalid MCS table={} for cell with pci={}\n", cell_cfg.pdsch_cfg.mcs_table, cell_cfg.pci); + report_error( + "Invalid MCS table={} for cell with pci={}\n", fmt::underlying(cell_cfg.pdsch_cfg.mcs_table), cell_cfg.pci); } // Generate zp-CSI-RS resources. @@ -547,25 +548,52 @@ std::vector srsran::generate_du_cell_config(const du_hig du_pucch_cfg.nof_cell_harq_pucch_res_sets = user_pucch_cfg.nof_cell_harq_pucch_sets; du_pucch_cfg.nof_sr_resources = user_pucch_cfg.nof_cell_sr_resources; du_pucch_cfg.nof_csi_resources = param.csi_rs_enabled ? user_pucch_cfg.nof_cell_csi_resources : 0U; + if (user_pucch_cfg.use_format_0) { auto& f0_params = du_pucch_cfg.f0_or_f1_params.emplace(); // Subtract 2 PUCCH resources from value: with Format 0, 2 extra resources will be added by the DU resource // allocator when the DU create the UE configuration. - du_pucch_cfg.nof_ue_pucch_f0_or_f1_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set - 2U; - du_pucch_cfg.nof_ue_pucch_f2_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set - 2U; - f0_params.intraslot_freq_hopping = user_pucch_cfg.f0_intraslot_freq_hopping; + const unsigned extra_res_harq = user_pucch_cfg.set1_format == 2U ? 2U : 0U; + du_pucch_cfg.nof_ue_pucch_f0_or_f1_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set - extra_res_harq; + du_pucch_cfg.nof_ue_pucch_f2_or_f3_or_f4_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set - extra_res_harq; + f0_params.intraslot_freq_hopping = user_pucch_cfg.f0_intraslot_freq_hopping; } else { auto& f1_params = du_pucch_cfg.f0_or_f1_params.emplace(); du_pucch_cfg.nof_ue_pucch_f0_or_f1_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set; - du_pucch_cfg.nof_ue_pucch_f2_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set; - f1_params.occ_supported = user_pucch_cfg.f1_enable_occ; - f1_params.nof_cyc_shifts = static_cast(user_pucch_cfg.nof_cyclic_shift); + du_pucch_cfg.nof_ue_pucch_f2_or_f3_or_f4_res_harq = user_pucch_cfg.nof_ue_pucch_res_harq_per_set; + f1_params.occ_supported = user_pucch_cfg.f1_enable_occ; + f1_params.nof_cyc_shifts = static_cast(user_pucch_cfg.f1_nof_cyclic_shifts); f1_params.intraslot_freq_hopping = user_pucch_cfg.f1_intraslot_freq_hopping; } - du_pucch_cfg.f2_params.max_code_rate = user_pucch_cfg.max_code_rate; - du_pucch_cfg.f2_params.max_nof_rbs = user_pucch_cfg.f2_max_nof_rbs; - du_pucch_cfg.f2_params.intraslot_freq_hopping = user_pucch_cfg.f2_intraslot_freq_hopping; - du_pucch_cfg.f2_params.max_payload_bits = user_pucch_cfg.max_payload_bits; + + switch (user_pucch_cfg.set1_format) { + case 2: { + auto& f2_params = du_pucch_cfg.f2_or_f3_or_f4_params.emplace(); + f2_params.max_code_rate = user_pucch_cfg.f2_max_code_rate; + f2_params.max_nof_rbs = user_pucch_cfg.f2_max_nof_rbs; + f2_params.intraslot_freq_hopping = user_pucch_cfg.f2_intraslot_freq_hopping; + f2_params.max_payload_bits = user_pucch_cfg.f2_max_payload_bits; + } break; + case 3: { + auto& f3_params = du_pucch_cfg.f2_or_f3_or_f4_params.emplace(); + f3_params.max_code_rate = user_pucch_cfg.f3_max_code_rate; + f3_params.max_nof_rbs = user_pucch_cfg.f3_max_nof_rbs; + f3_params.intraslot_freq_hopping = user_pucch_cfg.f3_intraslot_freq_hopping; + f3_params.max_payload_bits = user_pucch_cfg.f3_max_payload_bits; + f3_params.additional_dmrs = user_pucch_cfg.f3_additional_dmrs; + f3_params.pi2_bpsk = user_pucch_cfg.f3_pi2_bpsk; + } break; + case 4: { + auto& f4_params = du_pucch_cfg.f2_or_f3_or_f4_params.emplace(); + f4_params.max_code_rate = user_pucch_cfg.f4_max_code_rate; + f4_params.intraslot_freq_hopping = user_pucch_cfg.f4_intraslot_freq_hopping; + f4_params.additional_dmrs = user_pucch_cfg.f4_additional_dmrs; + f4_params.pi2_bpsk = user_pucch_cfg.f4_pi2_bpsk; + f4_params.occ_length = static_cast(user_pucch_cfg.f4_occ_length); + } break; + default: + break; + } // Parameters for SRS-Config. srs_du::srs_builder_params& du_srs_cfg = out_cell.srs_cfg; @@ -675,10 +703,18 @@ std::vector srsran::generate_du_cell_config(const du_hig // the configuration; therefore, the maximum number of symbols for PUCCH resources is computed only for periodic // SRS. du_pucch_cfg.max_nof_symbols = config_helpers::compute_max_nof_pucch_symbols(du_srs_cfg); - if (user_srs_cfg.srs_period_ms.has_value() and - std::holds_alternative(du_pucch_cfg.f0_or_f1_params)) { - auto& f1_params = std::get(du_pucch_cfg.f0_or_f1_params); - f1_params.nof_symbols = std::min(du_pucch_cfg.max_nof_symbols.to_uint(), f1_params.nof_symbols.to_uint()); + if (user_srs_cfg.srs_period_ms.has_value()) { + if (std::holds_alternative(du_pucch_cfg.f0_or_f1_params)) { + auto& f1_params = std::get(du_pucch_cfg.f0_or_f1_params); + f1_params.nof_symbols = std::min(du_pucch_cfg.max_nof_symbols.to_uint(), f1_params.nof_symbols.to_uint()); + } + if (std::holds_alternative(du_pucch_cfg.f2_or_f3_or_f4_params)) { + auto& f3_params = std::get(du_pucch_cfg.f2_or_f3_or_f4_params); + f3_params.nof_symbols = std::min(du_pucch_cfg.max_nof_symbols.to_uint(), f3_params.nof_symbols.to_uint()); + } else if (std::holds_alternative(du_pucch_cfg.f2_or_f3_or_f4_params)) { + auto& f4_params = std::get(du_pucch_cfg.f2_or_f3_or_f4_params); + f4_params.nof_symbols = std::min(du_pucch_cfg.max_nof_symbols.to_uint(), f4_params.nof_symbols.to_uint()); + } } if (update_msg1_frequency_start) { rach_cfg.rach_cfg_generic.msg1_frequency_start = config_helpers::compute_prach_frequency_start( @@ -891,11 +927,12 @@ scheduler_expert_config srsran::generate_scheduler_expert_config(const du_high_u if (std::holds_alternative(app_sched_expert_cfg.policy_sched_expert_cfg)) { out_cfg.ue.strategy_cfg = app_sched_expert_cfg.policy_sched_expert_cfg; } - out_cfg.ue.target_pusch_sinr = pusch.target_pusch_sinr; - out_cfg.ue.path_loss_for_target_pusch_sinr = pusch.path_loss_for_target_pusch_sinr; - out_cfg.ue.ta_cmd_offset_threshold = app_sched_expert_cfg.ta_sched_cfg.ta_cmd_offset_threshold; - out_cfg.ue.ta_target = app_sched_expert_cfg.ta_sched_cfg.ta_target; - out_cfg.ue.ta_measurement_slot_period = app_sched_expert_cfg.ta_sched_cfg.ta_measurement_slot_period; + out_cfg.ue.ul_power_ctrl.enable_pusch_cl_pw_control = pusch.enable_closed_loop_pw_control; + out_cfg.ue.ul_power_ctrl.target_pusch_sinr = pusch.target_pusch_sinr; + out_cfg.ue.ul_power_ctrl.path_loss_for_target_pusch_sinr = pusch.path_loss_for_target_pusch_sinr; + out_cfg.ue.ta_cmd_offset_threshold = app_sched_expert_cfg.ta_sched_cfg.ta_cmd_offset_threshold; + out_cfg.ue.ta_target = app_sched_expert_cfg.ta_sched_cfg.ta_target; + out_cfg.ue.ta_measurement_slot_period = app_sched_expert_cfg.ta_sched_cfg.ta_measurement_slot_period; out_cfg.ue.ta_update_measurement_ul_sinr_threshold = app_sched_expert_cfg.ta_sched_cfg.ta_update_measurement_ul_sinr_threshold; diff --git a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_validator.cpp b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_validator.cpp index 90a11c83af..88feb567ac 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_validator.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_validator.cpp @@ -29,7 +29,6 @@ #include "srsran/ran/pucch/pucch_info.h" #include "srsran/ran/transform_precoding/transform_precoding_helpers.h" #include "srsran/rlc/rlc_config.h" -#include "srsran/support/format/fmt_optional.h" #include using namespace srsran; @@ -223,7 +222,7 @@ static bool validate_pdcch_unit_config(const du_high_unit_base_cell_config& base "cell bandwidth={}Mhz\n", cs0_idx, ss0_idx, - base_cell.channel_bw_mhz); + fmt::underlying(base_cell.channel_bw_mhz)); return false; } // NOTE: The CORESET duration of 3 symbols is only permitted if the dmrs-typeA-Position information element has @@ -406,18 +405,25 @@ static bool validate_pusch_cell_unit_config(const du_high_unit_pusch_config& con /// Validates the given PUCCH cell application configuration. Returns true on success, otherwise false. static bool validate_pucch_cell_unit_config(const du_high_unit_base_cell_config& config, subcarrier_spacing scs_common, - unsigned nof_crbs) + unsigned nof_crbs, + bool ntn) { const du_high_unit_pucch_config& pucch_cfg = config.pucch_cfg; if (not config.csi_cfg.csi_rs_enabled and pucch_cfg.nof_cell_csi_resources > 0) { fmt::print( - "Number of PUCCH Format 2 cell resources for CSI must be zero when CSI-RS and CSI report are disabled.\n"); + "Number of PUCCH Format 2/3/4 cell resources for CSI must be zero when CSI-RS and CSI report are disabled.\n"); return false; } if (config.csi_cfg.csi_rs_enabled and pucch_cfg.nof_cell_csi_resources == 0) { - fmt::print("Number of PUCCH Format 2 cell resources for CSI must be greater than 0 when CSI-RS and CSI report are " - "enabled.\n"); + fmt::print( + "Number of PUCCH Format 2/3/4 cell resources for CSI must be greater than 0 when CSI-RS and CSI report are " + "enabled.\n"); + return false; + } + + if (pucch_cfg.use_format_0 and pucch_cfg.set1_format != 2) { + fmt::print("Using PUCCH Formats 3 and 4 is not supported when Format 0 is used.\n"); return false; } @@ -438,18 +444,20 @@ static bool validate_pucch_cell_unit_config(const du_high_unit_base_cell_config& get_nof_slots_per_subframe(scs_common)); return false; } - span valid_sr_period_slots = mu_to_valid_sr_period_slots_lookup.at(to_numerology_value(scs_common)); - if (std::find(valid_sr_period_slots.begin(), valid_sr_period_slots.end(), sr_period_slots) == - valid_sr_period_slots.end()) { - fmt::print("SR period of {}ms (i.e. {} slots) is not valid for {}kHz SCS.\n", - pucch_cfg.sr_period_msec, - sr_period_slots, - scs_to_khz(scs_common)); - return false; + if (!ntn) { + span valid_sr_period_slots = mu_to_valid_sr_period_slots_lookup.at(to_numerology_value(scs_common)); + if (std::find(valid_sr_period_slots.begin(), valid_sr_period_slots.end(), sr_period_slots) == + valid_sr_period_slots.end()) { + fmt::print("SR period of {}ms (i.e. {} slots) is not valid for {}kHz SCS.\n", + pucch_cfg.sr_period_msec, + sr_period_slots, + scs_to_khz(scs_common)); + return false; + } } // We need to count pucch_cfg.nof_ue_pucch_res_harq_per_set twice, as we have 2 sets of PUCCH resources for HARQ-ACK - // (PUCCH Resource Set Id 0 with Format 0/1 and PUCCH Resource Set Id 1 with Format 2). + // (PUCCH Resource Set Id 0 with Format 0/1 and PUCCH Resource Set Id 1 with Format 2/3/4). if (pucch_cfg.nof_ue_pucch_res_harq_per_set * 2U * pucch_cfg.nof_cell_harq_pucch_sets + pucch_cfg.nof_cell_sr_resources + pucch_cfg.nof_cell_csi_resources > pucch_constants::MAX_NOF_CELL_PUCCH_RESOURCES) { @@ -475,7 +483,7 @@ static bool validate_pucch_cell_unit_config(const du_high_unit_base_cell_config& pucch_cfg.nof_cell_sr_resources) / static_cast(nof_f0_per_block))); // With intraslot_freq_hopping, the nof of RBs is an even number. - if (config.pucch_cfg.f0_intraslot_freq_hopping) { + if (pucch_cfg.f0_intraslot_freq_hopping) { nof_f0_f1_rbs = static_cast(std::ceil(static_cast(nof_f0_f1_rbs) / 2.0F)) * 2; } } else { @@ -483,47 +491,90 @@ static bool validate_pucch_cell_unit_config(const du_high_unit_base_cell_config& // symbols available for PUCCH within a slot. const unsigned pucch_f1_nof_symbols = max_nof_pucch_symbols; const unsigned nof_occ_codes = - config.pucch_cfg.f1_enable_occ ? format1_symb_to_spreading_factor(pucch_f1_nof_symbols) : 1U; + pucch_cfg.f1_enable_occ ? format1_symb_to_spreading_factor(pucch_f1_nof_symbols) : 1U; // We define a block as a set of Resources (either F0/F1 or F2) aligned over the same starting PRB. - const unsigned nof_f1_per_block = nof_occ_codes * config.pucch_cfg.nof_cyclic_shift; + const unsigned nof_f1_per_block = nof_occ_codes * pucch_cfg.f1_nof_cyclic_shifts; // Each PUCCH resource F0/F1 occupies 1 RB (per block). - nof_f0_f1_rbs = static_cast(std::ceil( - static_cast(config.pucch_cfg.nof_ue_pucch_res_harq_per_set * pucch_cfg.nof_cell_harq_pucch_sets + - pucch_cfg.nof_cell_sr_resources) / - static_cast(nof_f1_per_block))); + nof_f0_f1_rbs = static_cast( + std::ceil(static_cast(pucch_cfg.nof_ue_pucch_res_harq_per_set * pucch_cfg.nof_cell_harq_pucch_sets + + pucch_cfg.nof_cell_sr_resources) / + static_cast(nof_f1_per_block))); // With intraslot_freq_hopping, the nof of RBs is an even number. - if (config.pucch_cfg.f1_intraslot_freq_hopping) { + if (pucch_cfg.f1_intraslot_freq_hopping) { nof_f0_f1_rbs = static_cast(std::ceil(static_cast(nof_f0_f1_rbs) / 2.0F)) * 2; } } - // The number of symbols per PUCCH resource F2 is not exposed to the DU user interface and set by default to 2. - constexpr unsigned pucch_f2_nof_symbols = 2U; - const unsigned f2_max_rbs = - config.pucch_cfg.max_payload_bits.has_value() - ? get_pucch_format2_max_nof_prbs(config.pucch_cfg.max_payload_bits.value(), - pucch_f2_nof_symbols, - to_max_code_rate_float(config.pucch_cfg.max_code_rate)) - : config.pucch_cfg.f2_max_nof_rbs; - - const unsigned nof_f2_blocks = max_nof_pucch_symbols / pucch_f2_nof_symbols; - unsigned nof_f2_rbs = - static_cast(std::ceil( - static_cast(config.pucch_cfg.nof_ue_pucch_res_harq_per_set * pucch_cfg.nof_cell_harq_pucch_sets + - pucch_cfg.nof_cell_csi_resources) / - static_cast(nof_f2_blocks))) * - f2_max_rbs; - // With intraslot_freq_hopping, the nof of RBs is an even number of the PUCCH resource size in RB. - if (config.pucch_cfg.f2_intraslot_freq_hopping) { - nof_f2_rbs = static_cast(std::ceil(static_cast(nof_f2_rbs) / 2.0F)) * 2; + unsigned nof_f2_f3_f4_rbs; + const unsigned nof_res_f2_f3_f4 = + pucch_cfg.nof_ue_pucch_res_harq_per_set * pucch_cfg.nof_cell_harq_pucch_sets + pucch_cfg.nof_cell_csi_resources; + switch (pucch_cfg.set1_format) { + case 2: { + // The number of symbols per PUCCH resource F2 is not exposed to the DU user interface and set by default to 2. + constexpr unsigned pucch_f2_nof_symbols = 2U; + const unsigned f2_max_rbs = + pucch_cfg.f2_max_payload_bits.has_value() + ? get_pucch_format2_max_nof_prbs(pucch_cfg.f2_max_payload_bits.value(), + pucch_f2_nof_symbols, + to_max_code_rate_float(pucch_cfg.f2_max_code_rate)) + : pucch_cfg.f2_max_nof_rbs; + + const unsigned nof_f2_blocks = max_nof_pucch_symbols / pucch_f2_nof_symbols; + nof_f2_f3_f4_rbs = + static_cast(std::ceil(static_cast(nof_res_f2_f3_f4) / static_cast(nof_f2_blocks))) * + f2_max_rbs; + // With intraslot_freq_hopping, the nof of RBs is an even number of the PUCCH resource size in RB. + if (pucch_cfg.f2_intraslot_freq_hopping) { + nof_f2_f3_f4_rbs = static_cast(std::ceil(static_cast(nof_f2_f3_f4_rbs) / 2.0F)) * 2; + } + } break; + case 3: { + // The number of symbols per PUCCH resource is not exposed to the DU user interface; for PUCCH F3, we use all + // symbols available for PUCCH within a slot. + const unsigned pucch_f3_nof_symbols = max_nof_pucch_symbols; + const unsigned f3_max_rbs = + pucch_cfg.f3_max_payload_bits.has_value() + ? get_pucch_format3_max_nof_prbs(pucch_cfg.f3_max_payload_bits.value(), + pucch_f3_nof_symbols, + to_max_code_rate_float(pucch_cfg.f3_max_code_rate), + // Since we are forcing 14 symbols intraslot_freq_hopping doesn't matter. + false, + pucch_cfg.f3_additional_dmrs, + pucch_cfg.f3_pi2_bpsk) + : pucch_cfg.f3_max_nof_rbs; + + const unsigned nof_f3_blocks = max_nof_pucch_symbols / pucch_f3_nof_symbols; + nof_f2_f3_f4_rbs = + static_cast(std::ceil(static_cast(nof_res_f2_f3_f4) / static_cast(nof_f3_blocks))) * + f3_max_rbs; + // With intraslot_freq_hopping, the nof of RBs is an even number of the PUCCH resource size in RB. + if (pucch_cfg.f3_intraslot_freq_hopping) { + nof_f2_f3_f4_rbs = static_cast(std::ceil(static_cast(nof_f2_f3_f4_rbs) / 2.0F)) * 2; + } + } break; + case 4: { + // The number of symbols per PUCCH resource is not exposed to the DU user interface; for PUCCH F4, we use all + // symbols available for PUCCH within a slot. + const unsigned pucch_f4_nof_symbols = max_nof_pucch_symbols; + const unsigned nof_f4_blocks = max_nof_pucch_symbols / pucch_f4_nof_symbols; + nof_f2_f3_f4_rbs = + static_cast(std::ceil(static_cast(nof_res_f2_f3_f4) / static_cast(nof_f4_blocks))); + // With intraslot_freq_hopping, the nof of RBs is an even number of the PUCCH resource size in RB. + if (pucch_cfg.f4_intraslot_freq_hopping) { + nof_f2_f3_f4_rbs = static_cast(std::ceil(static_cast(nof_f2_f3_f4_rbs) / 2.0F)) * 2; + } + } break; + default: + fmt::print("Invalid PUCCH format for Set Id 1.\n"); + return false; } // Verify the number of RBs for the PUCCH resources does not exceed the BWP size. // [Implementation-defined] We do not allow the PUCCH resources to occupy more than 50% of the BWP. This is an extreme // case, and ideally the PUCCH configuration should result in a much lower PRBs usage. constexpr float max_allowed_prbs_usage = 0.5F; - if (static_cast(nof_f0_f1_rbs + nof_f2_rbs) / static_cast(nof_crbs) >= max_allowed_prbs_usage) { + if (static_cast(nof_f0_f1_rbs + nof_f2_f3_f4_rbs) / static_cast(nof_crbs) >= max_allowed_prbs_usage) { fmt::print("With the given parameters, the number of PRBs for PUCCH exceeds the 50% of the BWP PRBs.\n"); return false; } @@ -794,14 +845,16 @@ static bool validate_dl_ul_arfcn_and_band(const du_high_unit_base_cell_config& c // Obtain the minimum bandwidth for the subcarrier and band combination. min_channel_bandwidth min_chan_bw = band_helper::get_min_channel_bw(band, config.common_scs); if (min_chan_bw == min_channel_bandwidth::invalid) { - fmt::print("Invalid combination for band n{} and subcarrier spacing {}.\n", band, to_string(config.common_scs)); + fmt::print("Invalid combination for band n{} and subcarrier spacing {}.\n", + fmt::underlying(band), + to_string(config.common_scs)); return false; } // Check that the configured bandwidth is greater than or equal to the minimum bandwidth if (bs_channel_bandwidth_to_MHz(config.channel_bw_mhz) < min_channel_bandwidth_to_MHz(min_chan_bw)) { fmt::print("Minimum supported bandwidth for n{} with SCS {} is {}MHz.\n", - band, + fmt::underlying(band), to_string(config.common_scs), min_channel_bandwidth_to_MHz(min_chan_bw)); return false; @@ -812,7 +865,8 @@ static bool validate_dl_ul_arfcn_and_band(const du_high_unit_base_cell_config& c error_type ret = band_helper::is_dl_arfcn_valid_given_band( *config.band, config.dl_f_ref_arfcn, config.common_scs, config.channel_bw_mhz); if (not ret.has_value()) { - fmt::print("Invalid DL ARFCN={} for band {}. Cause: {}.\n", config.dl_f_ref_arfcn, band, ret.error()); + fmt::print( + "Invalid DL ARFCN={} for band {}. Cause: {}.\n", config.dl_f_ref_arfcn, fmt::underlying(band), ret.error()); return false; } // Check if also the corresponding UL ARFCN is valid. @@ -820,7 +874,8 @@ static bool validate_dl_ul_arfcn_and_band(const du_high_unit_base_cell_config& c ret = band_helper::is_ul_arfcn_valid_given_band(*config.band, ul_arfcn, config.channel_bw_mhz); if (not ret.has_value()) { // NOTE: The message must say that it's the DL ARFCN that is invalid, as that is the parameters set by the user. - fmt::print("Invalid DL ARFCN={} for band {}. Cause: {}.\n", config.dl_f_ref_arfcn, band, ret.error()); + fmt::print( + "Invalid DL ARFCN={} for band {}. Cause: {}.\n", config.dl_f_ref_arfcn, fmt::underlying(band), ret.error()); return false; } } else { @@ -890,7 +945,7 @@ static bool validate_cell_sib_config(const du_high_unit_base_cell_config& cell_c } /// Validates the given cell application configuration. Returns true on success, otherwise false. -static bool validate_base_cell_unit_config(const du_high_unit_base_cell_config& config) +static bool validate_base_cell_unit_config(const du_high_unit_base_cell_config& config, bool ntn) { if (config.pci >= INVALID_PCI) { fmt::print("Invalid PCI (i.e. {}). PCI ranges from 0 to {}.\n", config.pci, MAX_PCI); @@ -953,7 +1008,7 @@ static bool validate_base_cell_unit_config(const du_high_unit_base_cell_config& return false; } - if (!validate_pucch_cell_unit_config(config, config.common_scs, nof_crbs)) { + if (!validate_pucch_cell_unit_config(config, config.common_scs, nof_crbs, ntn)) { return false; } @@ -988,17 +1043,17 @@ static bool validate_base_cell_unit_config(const du_high_unit_base_cell_config& return true; } -static bool validate_cell_unit_config(const du_high_unit_cell_config& config) +static bool validate_cell_unit_config(const du_high_unit_cell_config& config, bool ntn) { - return validate_base_cell_unit_config(config.cell); + return validate_base_cell_unit_config(config.cell, ntn); } /// Validates the given list of cell application configuration. Returns true on success, otherwise false. -static bool validate_cells_unit_config(span config, const gnb_id_t& gnb_id) +static bool validate_cells_unit_config(span config, const gnb_id_t& gnb_id, bool ntn) { - unsigned tac = config[0].cell.tac; + tac_t tac = config[0].cell.tac; for (const auto& cell : config) { - if (!validate_cell_unit_config(cell)) { + if (!validate_cell_unit_config(cell, ntn)) { return false; } if (cell.cell.tac != tac) { @@ -1317,7 +1372,13 @@ static bool validate_qos_config(span config) bool srsran::validate_du_high_config(const du_high_unit_config& config, const os_sched_affinity_bitmask& available_cpus) { - if (!validate_cells_unit_config(config.cells_cfg, config.gnb_id)) { + bool ntn = false; + if (config.ntn_cfg.has_value()) { + if (config.ntn_cfg.has_value()) { + ntn = true; + } + } + if (!validate_cells_unit_config(config.cells_cfg, config.gnb_id, ntn)) { return false; } diff --git a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_yaml_writer.cpp b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_yaml_writer.cpp index d15f543152..22d2716e33 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_yaml_writer.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/du_high_config_yaml_writer.cpp @@ -419,25 +419,39 @@ static YAML::Node build_du_high_pucch_section(const du_high_unit_pucch_config& c { YAML::Node node; - node["p0_nominal"] = config.p0_nominal; - node["pucch_resource_common"] = config.pucch_resource_common; - node["use_format_0"] = config.use_format_0; - node["sr_period_ms"] = config.sr_period_msec; - node["nof_ue_pucch_res_harq_per_set"] = config.nof_ue_pucch_res_harq_per_set; - node["f0_or_f1_nof_cell_res_sr"] = config.nof_cell_sr_resources; - node["f0_intraslot_freq_hop"] = config.f0_intraslot_freq_hopping; - node["f1_enable_occ"] = config.f1_enable_occ; - node["f1_nof_cyclic_shifts"] = config.nof_cyclic_shift; - node["f1_intraslot_freq_hop"] = config.f1_intraslot_freq_hopping; - node["nof_cell_harq_pucch_res_sets"] = config.nof_cell_harq_pucch_sets; - node["f2_nof_cell_res_csi"] = config.nof_cell_csi_resources; - node["f2_max_nof_rbs"] = config.f2_max_nof_rbs; - node["f2_max_code_rate"] = to_string(config.max_code_rate); - node["f2_intraslot_freq_hop"] = config.f2_intraslot_freq_hopping; - node["min_k1"] = config.min_k1; - node["max_consecutive_kos"] = config.max_consecutive_kos; - if (config.max_payload_bits.has_value()) { - node["f2_max_payload"] = config.max_payload_bits.value(); + node["p0_nominal"] = config.p0_nominal; + node["pucch_resource_common"] = config.pucch_resource_common; + node["use_format_0"] = config.use_format_0; + node["pucch_set1_format"] = config.set1_format; + node["sr_period_ms"] = config.sr_period_msec; + node["nof_ue_pucch_res_harq_per_set"] = config.nof_ue_pucch_res_harq_per_set; + node["f0_or_f1_nof_cell_res_sr"] = config.nof_cell_sr_resources; + node["f0_intraslot_freq_hop"] = config.f0_intraslot_freq_hopping; + node["f1_enable_occ"] = config.f1_enable_occ; + node["f1_nof_cyclic_shifts"] = config.f1_nof_cyclic_shifts; + node["f1_intraslot_freq_hop"] = config.f1_intraslot_freq_hopping; + node["nof_cell_harq_pucch_res_sets"] = config.nof_cell_harq_pucch_sets; + node["f2_or_f3_or_f4_nof_cell_res_csi"] = config.nof_cell_csi_resources; + node["f2_max_nof_rbs"] = config.f2_max_nof_rbs; + node["f2_max_code_rate"] = to_string(config.f2_max_code_rate); + node["f2_intraslot_freq_hop"] = config.f2_intraslot_freq_hopping; + node["f3_max_nof_rbs"] = config.f3_max_nof_rbs; + node["f3_max_code_rate"] = to_string(config.f3_max_code_rate); + node["f3_intraslot_freq_hop"] = config.f3_intraslot_freq_hopping; + node["f3_additional_dmrs"] = config.f3_additional_dmrs; + node["f3_pi2_bpsk"] = config.f3_pi2_bpsk; + node["f4_intraslot_freq_hop"] = config.f4_intraslot_freq_hopping; + node["f4_max_code_rate"] = to_string(config.f4_max_code_rate); + node["f4_additional_dmrs"] = config.f4_additional_dmrs; + node["f4_pi2_bpsk"] = config.f4_pi2_bpsk; + node["f4_occ_length"] = config.f4_occ_length; + node["min_k1"] = config.min_k1; + node["max_consecutive_kos"] = config.max_consecutive_kos; + if (config.f2_max_payload_bits.has_value()) { + node["f2_max_payload"] = config.f2_max_payload_bits.value(); + } + if (config.f3_max_payload_bits.has_value()) { + node["f3_max_payload"] = config.f3_max_payload_bits.value(); } return node; diff --git a/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_rlc_metrics_consumers.cpp b/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_rlc_metrics_consumers.cpp index 6ab669ba4d..35672be255 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_rlc_metrics_consumers.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_rlc_metrics_consumers.cpp @@ -115,7 +115,7 @@ void rlc_metrics_consumer_json::handle_metric(const app_services::metrics_set& m auto& output = ctx.get().back(); output.write(static_cast(drb.du_index)); - output.write(drb.ue_index); + output.write(static_cast(drb.ue_index)); output.write(static_cast(drb.rb_id.get_drb_id())); // TX metrics @@ -156,13 +156,13 @@ void rlc_metrics_consumer_log::handle_metric(const app_services::metrics_set& me const rlc_metrics& drb = static_cast(metric).get_metrics(); fmt::memory_buffer buffer; - fmt::format_to(buffer, "RLC Metrics:"); - fmt::format_to(buffer, " du={}", static_cast(drb.du_index)); - fmt::format_to(buffer, " ue={}", drb.ue_index); - fmt::format_to(buffer, " rb={}", drb.rb_id); - fmt::format_to(buffer, " mode={}", drb.rx.mode); - fmt::format_to(buffer, " TX=[{}]", format_rlc_tx_metrics(drb.metrics_period, drb.tx)); - fmt::format_to(buffer, " RX=[{}] ", format_rlc_rx_metrics(drb.metrics_period, drb.rx)); + fmt::format_to(std::back_inserter(buffer), "RLC Metrics:"); + fmt::format_to(std::back_inserter(buffer), " du={}", static_cast(drb.du_index)); + fmt::format_to(std::back_inserter(buffer), " ue={}", static_cast(drb.ue_index)); + fmt::format_to(std::back_inserter(buffer), " rb={}", drb.rb_id); + fmt::format_to(std::back_inserter(buffer), " mode={}", drb.rx.mode); + fmt::format_to(std::back_inserter(buffer), " TX=[{}]", format_rlc_tx_metrics(drb.metrics_period, drb.tx)); + fmt::format_to(std::back_inserter(buffer), " RX=[{}] ", format_rlc_rx_metrics(drb.metrics_period, drb.rx)); logger.info("{}", to_c_str(buffer)); } diff --git a/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_scheduler_cell_metrics_consumers.cpp b/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_scheduler_cell_metrics_consumers.cpp index 1c89c81a03..c22cfe1ae5 100644 --- a/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_scheduler_cell_metrics_consumers.cpp +++ b/apps/units/flexible_o_du/o_du_high/du_high/metrics/du_high_scheduler_cell_metrics_consumers.cpp @@ -24,8 +24,8 @@ #include "du_high_rlc_metrics.h" #include "srsran/scheduler/scheduler_metrics.h" #include "srsran/support/engineering_notation.h" -#include "srsran/support/math/math_utils.h" -#include +#include "fmt/ranges.h" +#include "fmt/std.h" #include using namespace srsran; @@ -299,6 +299,12 @@ void scheduler_cell_metrics_consumer_json::handle_metric(const app_services::met log_chan(ctx); } +template +static ResultType to_percentage(unsigned numerator, unsigned denominator) +{ + return static_cast(100.0 * static_cast(numerator) / static_cast(denominator)); +} + void scheduler_cell_metrics_consumer_log::handle_metric(const app_services::metrics_set& metric) { const scheduler_cell_metrics& metrics = static_cast(metric).get_metrics(); @@ -313,20 +319,24 @@ void scheduler_cell_metrics_consumer_log::handle_metric(const app_services::metr } // log cell-wide metrics - fmt::format_to(buffer, "Cell Scheduler Metrics:"); + fmt::format_to(std::back_inserter(buffer), "Cell Scheduler Metrics:"); fmt::format_to( - buffer, - " total_dl_bitrate_kbps={} total_ul_bitrate_kbps={} error_indications={} mean_latency={}usec latency_hist=[{}]", + std::back_inserter(buffer), + " total_dl_brate={}bps total_ul_brate={}bps nof_prbs={} nof_dl_slots={} nof_ul_slots={} error_indications={} " + "mean_latency={}usec latency_hist=[{}]", float_to_eng_string(sum_dl_bitrate_kbps * 1e3, 1, false), float_to_eng_string(sum_ul_bitrate_kbps * 1e3, 1, false), + metrics.nof_prbs, + metrics.nof_dl_slots, + metrics.nof_ul_slots, metrics.nof_error_indications, metrics.average_decision_latency.count(), fmt::join(metrics.latency_histogram.begin(), metrics.latency_histogram.end(), ", ")); if (not metrics.events.empty()) { - fmt::format_to(buffer, " events=["); + fmt::format_to(std::back_inserter(buffer), " events=["); bool first = true; for (const auto& event : metrics.events) { - fmt::format_to(buffer, + fmt::format_to(std::back_inserter(buffer), "{}{{rnti={} slot={} type={}}}", first ? "" : ", ", event.rnti, @@ -334,101 +344,109 @@ void scheduler_cell_metrics_consumer_log::handle_metric(const app_services::metr event_to_string(event.type)); first = false; } - fmt::format_to(buffer, "]"); + fmt::format_to(std::back_inserter(buffer), "]"); } logger.info("{}", to_c_str(buffer)); buffer.clear(); // log ue-specific metrics for (const auto& ue : metrics.ue_metrics) { - fmt::format_to(buffer, "Scheduler UE Metrics:"); - fmt::format_to(buffer, " pci={}", ue.pci); - fmt::format_to(buffer, " rnti={:x}", to_value(ue.rnti)); + fmt::format_to(std::back_inserter(buffer), "Scheduler UE Metrics:"); + fmt::format_to(std::back_inserter(buffer), " pci={}", ue.pci); + fmt::format_to(std::back_inserter(buffer), " rnti={:x}", to_value(ue.rnti)); if (ue.cqi_stats.get_nof_observations() > 0) { - fmt::format_to(buffer, " cqi={}", static_cast(std::roundf(ue.cqi_stats.get_mean()))); + fmt::format_to( + std::back_inserter(buffer), " cqi={}", static_cast(std::roundf(ue.cqi_stats.get_mean()))); } else { - fmt::format_to(buffer, " cqi=n/a"); + fmt::format_to(std::back_inserter(buffer), " cqi=n/a"); } if (ue.ri_stats.get_nof_observations() > 0) { - fmt::format_to(buffer, " ri={:.1f}", ue.ri_stats.get_mean()); + fmt::format_to(std::back_inserter(buffer), " ri={:.1f}", ue.ri_stats.get_mean()); } else { - fmt::format_to(buffer, " ri=n/a"); + fmt::format_to(std::back_inserter(buffer), " ri=n/a"); } - fmt::format_to(buffer, " dl_mcs={}", int(ue.dl_mcs.to_uint())); + fmt::format_to(std::back_inserter(buffer), " dl_mcs={}", int(ue.dl_mcs.to_uint())); if (ue.dl_brate_kbps > 0) { - fmt::format_to(buffer, " dl_brate_kbps={}", float_to_eng_string(ue.dl_brate_kbps * 1e3, 1, false)); + fmt::format_to( + std::back_inserter(buffer), " dl_brate={}bps", float_to_eng_string(ue.dl_brate_kbps * 1e3, 1, false)); } else { - fmt::format_to(buffer, " dl_brate_kbps={}", 0); + fmt::format_to(std::back_inserter(buffer), " dl_brate={}bps", 0); } - fmt::format_to(buffer, " dl_nof_ok={}", ue.dl_nof_ok); - fmt::format_to(buffer, " dl_nof_nok={}", ue.dl_nof_nok); + fmt::format_to(std::back_inserter(buffer), " dl_nof_ok={}", ue.dl_nof_ok); + fmt::format_to(std::back_inserter(buffer), " dl_nof_nok={}", ue.dl_nof_nok); unsigned dl_total = ue.dl_nof_ok + ue.dl_nof_nok; - if (dl_total > 0) { - fmt::format_to(buffer, " dl_error_rate={}%", int((float)100 * ue.dl_nof_nok / dl_total)); - } else { - fmt::format_to(buffer, " dl_error_rate={}%", 0); - } - fmt::format_to(buffer, " dl_bs={}", scaled_fmt_integer(ue.dl_bs, false)); - fmt::format_to(buffer, " dl_nof_prbs={}", ue.tot_dl_prbs_used); + fmt::format_to(std::back_inserter(buffer), + " dl_error_rate={}%", + dl_total > 0 ? to_percentage(ue.dl_nof_nok, dl_total) : 0); + fmt::format_to(std::back_inserter(buffer), " dl_bs={}", scaled_fmt_integer(ue.dl_bs, false)); + fmt::format_to(std::back_inserter(buffer), " dl_nof_prbs={}", ue.tot_dl_prbs_used); if (ue.last_dl_olla.has_value()) { - fmt::format_to(buffer, " dl_olla={}", ue.last_dl_olla); + fmt::format_to(std::back_inserter(buffer), " dl_olla={}", ue.last_dl_olla); } if (!std::isnan(ue.pusch_snr_db) && !iszero(ue.pusch_snr_db)) { - fmt::format_to(buffer, " pusch_snr_db={:.1f}", std::clamp(ue.pusch_snr_db, -99.9f, 99.9f)); + fmt::format_to(std::back_inserter(buffer), " pusch_snr_db={:.1f}", std::clamp(ue.pusch_snr_db, -99.9f, 99.9f)); } else { - fmt::format_to(buffer, " pusch_snr_db=n/a"); + fmt::format_to(std::back_inserter(buffer), " pusch_snr_db=n/a"); } if (!std::isinf(ue.pusch_rsrp_db) && !std::isnan(ue.pusch_rsrp_db)) { if (ue.pusch_rsrp_db >= 0.0F) { - fmt::format_to(buffer, " pusch_rsrp_db=ovl"); + fmt::format_to(std::back_inserter(buffer), " pusch_rsrp_db=ovl"); } else { - fmt::format_to(buffer, " pusch_rsrp_db={:.1f}", std::clamp(ue.pusch_rsrp_db, -99.9F, 0.0F)); + fmt::format_to(std::back_inserter(buffer), " pusch_rsrp_db={:.1f}", std::clamp(ue.pusch_rsrp_db, -99.9F, 0.0F)); } } else { - fmt::format_to(buffer, " pusch_rsrp_db=n/a"); + fmt::format_to(std::back_inserter(buffer), " pusch_rsrp_db=n/a"); } - fmt::format_to(buffer, " ul_mcs={}", ue.ul_mcs.to_uint()); + fmt::format_to(std::back_inserter(buffer), " ul_mcs={}", ue.ul_mcs.to_uint()); if (ue.ul_brate_kbps > 0) { - fmt::format_to(buffer, " ul_brate_kbps={}", float_to_eng_string(ue.ul_brate_kbps * 1e3, 1, false)); + fmt::format_to( + std::back_inserter(buffer), " ul_brate={}bps", float_to_eng_string(ue.ul_brate_kbps * 1e3, 1, false)); } else { - fmt::format_to(buffer, " ul_brate_kbps={}", 0); + fmt::format_to(std::back_inserter(buffer), " ul_brate={}bps", 0); } - fmt::format_to(buffer, " ul_nof_ok={}", ue.ul_nof_ok); - fmt::format_to(buffer, " ul_nof_nok={}", ue.ul_nof_nok); + fmt::format_to(std::back_inserter(buffer), " ul_nof_ok={}", ue.ul_nof_ok); + fmt::format_to(std::back_inserter(buffer), " ul_nof_nok={}", ue.ul_nof_nok); unsigned ul_total = ue.ul_nof_ok + ue.ul_nof_nok; + fmt::format_to(std::back_inserter(buffer), + " ul_error_rate={}%", + ul_total > 0 ? to_percentage(ue.ul_nof_nok, ul_total) : 0); if (ul_total > 0) { - fmt::format_to(buffer, " ul_error_rate={}%", int((float)100 * ue.ul_nof_nok / ul_total)); - fmt::format_to(buffer, " crc_delay_ms={}", ue.ul_delay_ms); + fmt::format_to(std::back_inserter(buffer), " crc_delay_ms={:.3}", ue.ul_delay_ms); } else { - fmt::format_to(buffer, " ul_error_rate={}%", 0); - fmt::format_to(buffer, " crc_delay_ms=n/a"); + fmt::format_to(std::back_inserter(buffer), " crc_delay_ms=n/a"); } - fmt::format_to(buffer, " ul_nof_prbs={}", ue.tot_ul_prbs_used); - fmt::format_to(buffer, " bsr={}", scaled_fmt_integer(ue.bsr, false)); - fmt::format_to(buffer, " sr_count={}", ue.sr_count); + fmt::format_to(std::back_inserter(buffer), " ul_nof_prbs={}", ue.tot_ul_prbs_used); + fmt::format_to(std::back_inserter(buffer), " bsr={}", scaled_fmt_integer(ue.bsr, false)); + fmt::format_to(std::back_inserter(buffer), " sr_count={}", ue.sr_count); if (ue.last_ul_olla.has_value()) { - fmt::format_to(buffer, " ul_olla={}", ue.last_ul_olla); + fmt::format_to(std::back_inserter(buffer), " ul_olla={}", ue.last_ul_olla); } if (ue.ta_stats.get_nof_observations() > 0) { - fmt::format_to(buffer, " ta={}s", float_to_eng_string(ue.ta_stats.get_mean(), 0, false)); + fmt::format_to(std::back_inserter(buffer), " ta={}s", float_to_eng_string(ue.ta_stats.get_mean(), 0, false)); } else { - fmt::format_to(buffer, " ta=n/a"); + fmt::format_to(std::back_inserter(buffer), " ta=n/a"); } if (ue.srs_ta_stats.get_nof_observations() > 0) { - fmt::format_to(buffer, " srs_ta={}s", float_to_eng_string(ue.srs_ta_stats.get_mean(), 0, false)); + fmt::format_to( + std::back_inserter(buffer), " srs_ta={}s", float_to_eng_string(ue.srs_ta_stats.get_mean(), 0, false)); } else { - fmt::format_to(buffer, " srs_ta=n/a"); + fmt::format_to(std::back_inserter(buffer), " srs_ta=n/a"); } if (ue.last_phr.has_value()) { - fmt::format_to(buffer, " last_phr={}", ue.last_phr.value()); + fmt::format_to(std::back_inserter(buffer), " last_phr={}", ue.last_phr.value()); + } else { + fmt::format_to(std::back_inserter(buffer), " last_phr=n/a"); + } + if (ue.mean_ce_delay_msec.has_value()) { + fmt::format_to(std::back_inserter(buffer), " ul_ce_delay={:.2}ms", ue.mean_ce_delay_msec.value()); } else { - fmt::format_to(buffer, " last_phr=n/a"); + fmt::format_to(std::back_inserter(buffer), " ul_ce_delay=n/a"); } logger.info("{}", to_c_str(buffer)); diff --git a/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.cpp b/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.cpp index a6141b9c3e..0bd937a8e3 100644 --- a/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.cpp +++ b/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.cpp @@ -65,7 +65,7 @@ void srsran::announce_du_high_cells(const du_high_unit_config& du_high_unit_cfg) "SSB derived parameters for cell: {}, band: {}, dl_arfcn:{}, nof_crbs: {} scs:{}, ssb_scs:{}:\n\t - SSB offset " "pointA:{} \n\t - k_SSB:{} \n\t - SSB arfcn:{} \n\t - Coreset index:{} \n\t - Searchspace index:{}", cell.pci, - cell.dl_carrier.band, + fmt::underlying(cell.dl_carrier.band), cell.dl_carrier.arfcn_f_ref, cell.dl_cfg_common.init_dl_bwp.generic_params.crbs.length(), to_string(cell.dl_cfg_common.init_dl_bwp.generic_params.scs), @@ -200,26 +200,23 @@ static rlc_metrics_notifier* build_rlc_du_metrics(std::vector((static_cast(du_high_unit_cfg.gnb_du_id) + o_du_high_unit_cfg.du_index)); - du_hi_cfg.ran.gnb_du_name = fmt::format("srsdu{}", du_hi_cfg.ran.gnb_du_id); + du_hi_cfg.ran.gnb_du_id = du_high_unit_cfg.gnb_du_id; + du_hi_cfg.ran.gnb_du_name = fmt::format("srsdu{}", fmt::underlying(du_hi_cfg.ran.gnb_du_id)); du_hi_cfg.ran.cells = generate_du_cell_config(du_high_unit_cfg); // Validates the derived parameters. validates_derived_du_params(du_hi_cfg.ran.cells); - du_hi_cfg.ran.srbs = generate_du_srb_config(du_high_unit_cfg); - du_hi_cfg.ran.qos = generate_du_qos_config(du_high_unit_cfg); - du_hi_cfg.ran.mac_cfg = generate_mac_expert_config(du_high_unit_cfg); - // Assign different initial C-RNTIs to different DUs. - du_hi_cfg.ran.mac_cfg.initial_crnti = to_rnti(0x4601 + (0x1000 * o_du_high_unit_cfg.du_index)); + du_hi_cfg.ran.srbs = generate_du_srb_config(du_high_unit_cfg); + du_hi_cfg.ran.qos = generate_du_qos_config(du_high_unit_cfg); + du_hi_cfg.ran.mac_cfg = generate_mac_expert_config(du_high_unit_cfg); + du_hi_cfg.ran.mac_cfg.initial_crnti = to_rnti(0x4601); du_hi_cfg.ran.sched_cfg = generate_scheduler_expert_config(du_high_unit_cfg); srs_du::du_high_dependencies& du_hi_deps = dependencies.o_du_hi_dependencies.du_hi; @@ -231,15 +228,15 @@ o_du_high_unit srsran::make_o_du_high_unit(const o_du_high_unit_params& o_du_hi du_hi_deps.mac_p = &dependencies.mac_p; du_hi_deps.rlc_p = &dependencies.rlc_p; - if (odu_unit_cfg.e2_cfg.base_cfg.enable_unit_e2) { + if (o_du_high_unit_cfg.e2_cfg.base_cfg.enable_unit_e2) { // Connect E2 agent to RLC metric source. dependencies.o_du_hi_dependencies.e2_client = &dependencies.e2_client_handler; - o_du_high_cfg.e2ap_config = generate_e2_config(odu_unit_cfg.e2_cfg, + o_du_high_cfg.e2ap_config = generate_e2_config(o_du_high_unit_cfg.e2_cfg, du_high_unit_cfg.gnb_id, du_high_unit_cfg.cells_cfg.front().cell.plmn, du_hi_cfg.ran.gnb_du_id); dependencies.o_du_hi_dependencies.e2_du_metric_iface = - &(dependencies.e2_metric_connectors.get_e2_metrics_interface(o_du_high_unit_cfg.du_index)); + &(dependencies.e2_metric_connectors.get_e2_metrics_interface(0)); } // DU high metrics. @@ -249,16 +246,15 @@ o_du_high_unit srsran::make_o_du_high_unit(const o_du_high_unit_params& o_du_hi build_scheduler_du_metrics(odu_unit.metrics, odu_unit.commands, dependencies.metrics_notifier, - odu_unit_cfg, + o_du_high_unit_cfg, dependencies.json_sink, - dependencies.e2_metric_connectors.get_e2_metric_notifier(o_du_high_unit_cfg.du_index)); + dependencies.e2_metric_connectors.get_e2_metric_notifier(0)); - du_hi_deps.rlc_metrics_notif = - build_rlc_du_metrics(odu_unit.metrics, - dependencies.metrics_notifier, - odu_unit_cfg, - dependencies.json_sink, - dependencies.e2_metric_connectors.get_e2_metric_notifier(o_du_high_unit_cfg.du_index)); + du_hi_deps.rlc_metrics_notif = build_rlc_du_metrics(odu_unit.metrics, + dependencies.metrics_notifier, + o_du_high_unit_cfg, + dependencies.json_sink, + dependencies.e2_metric_connectors.get_e2_metric_notifier(0)); // Configure test mode if (du_high_unit_cfg.test_mode_cfg.test_ue.rnti != rnti_t::INVALID_RNTI) { @@ -277,7 +273,7 @@ o_du_high_unit srsran::make_o_du_high_unit(const o_du_high_unit_params& o_du_hi } // FAPI configuration. - const fapi_unit_config& fapi_cfg = o_du_high_unit_cfg.o_du_hi_cfg.fapi_cfg; + const fapi_unit_config& fapi_cfg = o_du_high_unit_cfg.fapi_cfg; o_du_high_cfg.fapi.log_level = fapi_cfg.fapi_level; o_du_high_cfg.fapi.l2_nof_slots_ahead = fapi_cfg.l2_nof_slots_ahead; diff --git a/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h b/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h index 2264bb990d..0dff6eba55 100644 --- a/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h +++ b/apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h @@ -68,12 +68,6 @@ struct o_du_high_unit { std::vector metrics; }; -/// O-RAN DU high unit parameters. -struct o_du_high_unit_params { - const o_du_high_unit_config& o_du_hi_cfg; - unsigned du_index; -}; - /// O-RAN DU high unit dependencies. struct o_du_high_unit_dependencies { srs_du::du_high_executor_mapper& execution_mapper; @@ -90,7 +84,7 @@ struct o_du_high_unit_dependencies { }; /// Creates the O-RAN DU high unit with the given configuration. -o_du_high_unit make_o_du_high_unit(const o_du_high_unit_params& o_du_high_unit_cfg, +o_du_high_unit make_o_du_high_unit(const o_du_high_unit_config& o_du_high_unit_cfg, o_du_high_unit_dependencies&& dependencies); } // namespace srsran diff --git a/apps/units/flexible_o_du/o_du_low/du_low_config_translator.cpp b/apps/units/flexible_o_du/o_du_low/du_low_config_translator.cpp index b2c29a6094..a839e0510c 100644 --- a/apps/units/flexible_o_du/o_du_low/du_low_config_translator.cpp +++ b/apps/units/flexible_o_du/o_du_low/du_low_config_translator.cpp @@ -33,8 +33,7 @@ using namespace srsran; static void generate_du_low_config(srs_du::du_low_config& out_config, const du_low_unit_config& du_low, span du_cells, - span max_puschs_per_slot, - unsigned du_id) + span max_puschs_per_slot) { out_config.cells.reserve(du_cells.size()); @@ -129,7 +128,7 @@ static void generate_du_low_config(srs_du::du_low_config& out_config upper_phy_cell.rx_symbol_printer_port = du_low.loggers.phy_rx_symbols_port; upper_phy_cell.rx_symbol_printer_prach = du_low.loggers.phy_rx_symbols_prach; upper_phy_cell.logger_max_hex_size = du_low.loggers.hex_max_size; - upper_phy_cell.sector_id = du_id + i; + upper_phy_cell.sector_id = i; upper_phy_cell.nof_tx_ports = cell.dl_carrier.nof_ant; upper_phy_cell.nof_rx_ports = cell.ul_carrier.nof_ant; upper_phy_cell.ldpc_decoder_iterations = du_low.expert_phy_cfg.pusch_decoder_max_iterations; @@ -172,10 +171,9 @@ static void generate_du_low_config(srs_du::du_low_config& out_config void srsran::generate_o_du_low_config(srs_du::o_du_low_config& out_config, const du_low_unit_config& du_low_unit_cfg, span du_cells, - span max_puschs_per_slot, - unsigned du_id) + span max_puschs_per_slot) { - generate_du_low_config(out_config.du_low_cfg, du_low_unit_cfg, du_cells, max_puschs_per_slot, du_id); + generate_du_low_config(out_config.du_low_cfg, du_low_unit_cfg, du_cells, max_puschs_per_slot); } void srsran::fill_du_low_worker_manager_config(worker_manager_config& config, diff --git a/apps/units/flexible_o_du/o_du_low/du_low_config_translator.h b/apps/units/flexible_o_du/o_du_low/du_low_config_translator.h index 26299c4d97..97e44fb434 100644 --- a/apps/units/flexible_o_du/o_du_low/du_low_config_translator.h +++ b/apps/units/flexible_o_du/o_du_low/du_low_config_translator.h @@ -38,8 +38,7 @@ struct worker_manager_config; void generate_o_du_low_config(srs_du::o_du_low_config& out_config, const du_low_unit_config& du_low_unit_cfg, span du_cells, - span max_puschs_per_slot, - unsigned du_id); + span max_puschs_per_slot); /// Fills the DU low worker manager parameters of the given worker manager configuration. void fill_du_low_worker_manager_config(worker_manager_config& config, diff --git a/apps/units/flexible_o_du/o_du_low/du_low_config_yaml_writer.cpp b/apps/units/flexible_o_du/o_du_low/du_low_config_yaml_writer.cpp index 39775a3fcd..3c81f3598c 100644 --- a/apps/units/flexible_o_du/o_du_low/du_low_config_yaml_writer.cpp +++ b/apps/units/flexible_o_du/o_du_low/du_low_config_yaml_writer.cpp @@ -83,9 +83,61 @@ static void fill_du_low_expert_section(YAML::Node node, const du_low_unit_expert node["max_request_headroom_slots"] = config.nof_slots_request_headroom; } +static void fill_du_low_bbdev_pdsch_enc_section(YAML::Node node, const hwacc_pdsch_appconfig& config) +{ + node["nof_hwacc"] = config.nof_hwacc; + node["cb_mode"] = config.cb_mode; + node["dedicated_queue"] = config.dedicated_queue; + if (config.max_buffer_size) { + node["max_buffer_size"] = config.max_buffer_size.value(); + } +} + +static void fill_du_low_bbdev_pusch_dec_section(YAML::Node node, const hwacc_pusch_appconfig& config) +{ + node["nof_hwacc"] = config.nof_hwacc; + node["ext_softbuffer"] = config.ext_softbuffer; + node["dedicated_queue"] = config.dedicated_queue; + if (config.harq_context_size) { + node["harq_context_size"] = config.harq_context_size.value(); + } +} + +static void fill_du_low_bbdev_section(YAML::Node node, const bbdev_appconfig& config) +{ + node["hwacc_type"] = config.hwacc_type; + node["id"] = config.id; + + if (config.pdsch_enc) { + fill_du_low_bbdev_pdsch_enc_section(node["pdsch_enc"], config.pdsch_enc.value()); + } + if (config.pusch_dec) { + fill_du_low_bbdev_pusch_dec_section(node["pusch_dec"], config.pusch_dec.value()); + } + if (config.msg_mbuf_size) { + node["msg_mbuf_size"] = config.msg_mbuf_size.value(); + } + if (config.rm_mbuf_size) { + node["rm_mbuf_size"] = config.rm_mbuf_size.value(); + } + if (config.nof_mbuf) { + node["nof_mbuf"] = config.nof_mbuf.value(); + } +} + +static void fill_du_low_hal_section(YAML::Node node, const du_low_unit_hal_config& config) +{ + if (config.bbdev_hwacc) { + fill_du_low_bbdev_section(node["bbdev_hwacc"], config.bbdev_hwacc.value()); + } +} + void srsran::fill_du_low_config_in_yaml_schema(YAML::Node& node, const du_low_unit_config& config) { fill_du_low_log_section(node["log"], config.loggers); fill_du_low_expert_execution_section(node["expert_execution"], config.expert_execution_cfg); fill_du_low_expert_section(node["expert_phy"], config.expert_phy_cfg); + if (config.hal_config) { + fill_du_low_hal_section(node["hal"], config.hal_config.value()); + } } diff --git a/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.cpp b/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.cpp index 0dc1fec171..4d12414a5d 100644 --- a/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.cpp +++ b/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.cpp @@ -83,8 +83,7 @@ o_du_low_unit o_du_low_unit_factory::create(const o_du_low_unit_config& pa srs_du::o_du_low_config o_du_low_cfg; o_du_low_cfg.du_low_cfg.logger = &srslog::fetch_basic_logger("DU"); - generate_o_du_low_config( - o_du_low_cfg, params.du_low_unit_cfg, params.du_cells, params.max_puschs_per_slot, params.du_id); + generate_o_du_low_config(o_du_low_cfg, params.du_low_unit_cfg, params.du_cells, params.max_puschs_per_slot); // Fill the PRACH ports. o_du_low_cfg.prach_ports = params.prach_ports; @@ -95,21 +94,21 @@ o_du_low_unit o_du_low_unit_factory::create(const o_du_low_unit_config& pa generate_dl_processor_config(cell.dl_proc_cfg, params.du_low_unit_cfg, - *dependencies.workers.upper_pdsch_exec[i + params.du_id], + *dependencies.workers.upper_pdsch_exec[i], hal_dependencies.hw_encoder_factory); upper_phy_config& upper = cell.upper_phy_cfg; upper.rg_gateway = &dependencies.rg_gateway; upper.rx_symbol_request_notifier = &dependencies.rx_symbol_request_notifier; - upper.pucch_executor = dependencies.workers.upper_pucch_exec[i + params.du_id]; - upper.pusch_executor = dependencies.workers.upper_pusch_exec[i + params.du_id]; - upper.pusch_decoder_executor = dependencies.workers.upper_pusch_decoder_exec[i + params.du_id]; - upper.prach_executor = dependencies.workers.upper_prach_exec[i + params.du_id]; - upper.srs_executor = dependencies.workers.upper_srs_exec[i + params.du_id]; + upper.pucch_executor = dependencies.workers.upper_pucch_exec[i]; + upper.pusch_executor = dependencies.workers.upper_pusch_exec[i]; + upper.pusch_decoder_executor = dependencies.workers.upper_pusch_decoder_exec[i]; + upper.prach_executor = dependencies.workers.upper_prach_exec[i]; + upper.srs_executor = dependencies.workers.upper_srs_exec[i]; if (hal_dependencies.hw_decoder_factory) { upper.hw_decoder_factory = hal_dependencies.hw_decoder_factory; } - dependencies.workers.get_du_low_dl_executors(upper.dl_executors, i + params.du_id); + dependencies.workers.get_du_low_dl_executors(upper.dl_executors, i); } o_du_low_unit unit; diff --git a/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h b/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h index e87ef34cc5..92f6836cfa 100644 --- a/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h +++ b/apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h @@ -47,8 +47,6 @@ struct o_du_low_unit_config { std::vector prach_ports; span du_cells; span max_puschs_per_slot; - unsigned du_id; - unsigned nof_cells; }; /// O-RAN DU low unit dependencies. diff --git a/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.cpp b/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.cpp index 1faea02073..470cba5ef7 100644 --- a/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.cpp +++ b/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.cpp @@ -67,8 +67,7 @@ void split6_o_du_application_unit_impl::on_parsing_configuration_registration(CL plugin->on_parsing_configuration_registration(app); } -o_du_unit split6_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, - bool use_multicell) +o_du_unit split6_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) { auto fapi_ctrl = plugin->create_fapi_adaptor(dependencies); report_error_if_not(!fapi_ctrl.empty(), "Could not create FAPI adaptor"); diff --git a/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.h b/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.h index 2b184a2044..21c359d4d2 100644 --- a/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.h +++ b/apps/units/flexible_o_du/split_6/split6_o_du_application_unit_impl.h @@ -47,7 +47,7 @@ class split6_o_du_application_unit_impl : public flexible_o_du_application_unit void on_loggers_registration() override; // See interface for documentation. - o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, bool use_multicell = false) override; + o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) override; // See interface for documentation. o_du_high_unit_config& get_o_du_high_unit_config() override { return unit_cfg.odu_high_cfg; } diff --git a/apps/units/flexible_o_du/split_6/split6_o_du_factory.cpp b/apps/units/flexible_o_du/split_6/split6_o_du_factory.cpp index 1338a5edd2..ca12fa0d9b 100644 --- a/apps/units/flexible_o_du/split_6/split6_o_du_factory.cpp +++ b/apps/units/flexible_o_du/split_6/split6_o_du_factory.cpp @@ -27,7 +27,6 @@ #include "apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.h" #include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_config.h" #include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h" -#include "apps/units/flexible_o_du/split_helpers/o_du_high_factory.h" #include "split6_o_du_impl.h" #include "split6_o_du_unit_config.h" #include "srsran/e2/e2_du_metrics_connector.h" @@ -43,44 +42,37 @@ o_du_unit srsran::create_o_du_split6(const split6_o_du_unit_config& e2_metric_connector_manager>( du_unit_cfg.odu_high_cfg.du_high_cfg.config.cells_cfg.size()); - std::vector du_high_insts; - - // Create O-DU high. - o_du_high_unit_factory_dependencies odu_hi_unit_dependencies = {*du_dependencies.workers, - *du_dependencies.f1c_client_handler, - *du_dependencies.f1u_gw, - *du_dependencies.timer_mng, - *du_dependencies.mac_p, - *du_dependencies.rlc_p, - *du_dependencies.e2_client_handler, - *(odu_unit.e2_metric_connectors), - *du_dependencies.json_sink, - *du_dependencies.metrics_notifier, - {}}; + o_du_high_unit_dependencies odu_hi_unit_dependencies = {du_dependencies.workers->get_du_high_executor_mapper(0), + *du_dependencies.f1c_client_handler, + *du_dependencies.f1u_gw, + *du_dependencies.timer_mng, + *du_dependencies.mac_p, + *du_dependencies.rlc_p, + *du_dependencies.e2_client_handler, + *(odu_unit.e2_metric_connectors), + *du_dependencies.json_sink, + *du_dependencies.metrics_notifier, + {}}; + // Adjust the dependencies. const auto& du_hi_unit_cfg = du_unit_cfg.odu_high_cfg.du_high_cfg.config; for (unsigned i = 0, e = du_hi_unit_cfg.cells_cfg.size(); i != e; ++i) { - auto& sector_deps = odu_hi_unit_dependencies.o_du_hi_dependencies.sectors.emplace_back(); - sector_deps.gateway = &fapi_adaptors[i]->get_message_interface_collection().get_slot_message_gateway(); - sector_deps.last_msg_notifier = + auto& sector_dependencies = odu_hi_unit_dependencies.o_du_hi_dependencies.sectors.emplace_back(); + sector_dependencies.gateway = &fapi_adaptors[i]->get_message_interface_collection().get_slot_message_gateway(); + sector_dependencies.last_msg_notifier = &fapi_adaptors[i]->get_message_interface_collection().get_slot_last_message_notifier(); - sector_deps.fapi_executor = du_unit_cfg.odu_high_cfg.fapi_cfg.l2_nof_slots_ahead != 0 - ? std::optional(du_dependencies.workers->fapi_exec[i]) - : std::make_optional(); + sector_dependencies.fapi_executor = du_unit_cfg.odu_high_cfg.fapi_cfg.l2_nof_slots_ahead != 0 + ? std::optional(du_dependencies.workers->fapi_exec[i]) + : std::make_optional(); } - du_high_insts = make_multicell_with_multi_du(du_unit_cfg.odu_high_cfg, std::move(odu_hi_unit_dependencies)); - - odu_unit.commands = std::move(du_high_insts.front().commands); - odu_unit.metrics = std::move(du_high_insts.front().metrics); + o_du_high_unit odu_hi_unit = make_o_du_high_unit(du_unit_cfg.odu_high_cfg, std::move(odu_hi_unit_dependencies)); - std::vector> odu_high_insts; - for (auto& sector : du_high_insts) { - odu_high_insts.push_back(std::move(sector.o_du_hi)); - } + odu_unit.commands = std::move(odu_hi_unit.commands); + odu_unit.metrics = std::move(odu_hi_unit.metrics); // Create the DU. - odu_unit.unit = std::make_unique(std::move(fapi_adaptors), std::move(odu_high_insts)); + odu_unit.unit = std::make_unique(std::move(fapi_adaptors), std::move(odu_hi_unit.o_du_hi)); report_error_if_not(odu_unit.unit, "Invalid Distributed Unit"); announce_du_high_cells(du_hi_unit_cfg); diff --git a/apps/units/flexible_o_du/split_6/split6_o_du_impl.cpp b/apps/units/flexible_o_du/split_6/split6_o_du_impl.cpp index 8073f6aaa4..d40a459da2 100644 --- a/apps/units/flexible_o_du/split_6/split6_o_du_impl.cpp +++ b/apps/units/flexible_o_du/split_6/split6_o_du_impl.cpp @@ -26,29 +26,27 @@ using namespace srsran; split6_o_du_impl::split6_o_du_impl(std::vector> adaptors_, - std::vector> du_list_) : - du_list(std::move(du_list_)), adaptors(std::move(adaptors_)) + std::unique_ptr odu_hi_) : + odu_hi(std::move(odu_hi_)), adaptors(std::move(adaptors_)) { - srsran_assert(!du_list.empty(), "Invalid DU high wrapper"); - srsran_assert(adaptors.size() == du_list.size(), "Number of FAPI adaptors does not match number of DUs"); + srsran_assert(odu_hi, "Invalid O-DU high"); + srsran_assert(adaptors.size(), "Invalid FAPI adaptor"); // Hardcoded 0 is because at this moment there is one DU instance with one cell per cell, so we can always access the // cell id 0 of the DU. - for (unsigned i = 0, e = du_list.size(); i != e; ++i) { + for (unsigned i = 0, e = adaptors.size(); i != e; ++i) { adaptors[i]->get_message_interface_collection().set_slot_data_message_notifier( - du_list[i]->get_slot_data_message_notifier(0)); + odu_hi->get_slot_data_message_notifier(i)); adaptors[i]->get_message_interface_collection().set_slot_error_message_notifier( - du_list[i]->get_slot_error_message_notifier(0)); + odu_hi->get_slot_error_message_notifier(i)); adaptors[i]->get_message_interface_collection().set_slot_time_message_notifier( - du_list[i]->get_slot_time_message_notifier(0)); + odu_hi->get_slot_time_message_notifier(i)); } } void split6_o_du_impl::start() { - for (auto& du_obj : du_list) { - du_obj->get_power_controller().start(); - } + odu_hi->get_power_controller().start(); for (auto& adaptor : adaptors) { adaptor->get_power_operation_controller().start(); @@ -57,9 +55,7 @@ void split6_o_du_impl::start() void split6_o_du_impl::stop() { - for (auto& du_obj : du_list) { - du_obj->get_power_controller().stop(); - } + odu_hi->get_power_controller().stop(); for (auto& adaptor : adaptors) { adaptor->get_power_operation_controller().stop(); diff --git a/apps/units/flexible_o_du/split_6/split6_o_du_impl.h b/apps/units/flexible_o_du/split_6/split6_o_du_impl.h index c608f25c9b..e7307f72cb 100644 --- a/apps/units/flexible_o_du/split_6/split6_o_du_impl.h +++ b/apps/units/flexible_o_du/split_6/split6_o_du_impl.h @@ -38,7 +38,7 @@ class split6_o_du_impl : public srs_du::du, public du_power_controller { public: explicit split6_o_du_impl(std::vector> adaptors_, - std::vector> du_list_); + std::unique_ptr odu_hi_); // See interface for documentation. du_power_controller& get_power_controller() override { return *this; } @@ -50,7 +50,7 @@ class split6_o_du_impl : public srs_du::du, public du_power_controller void stop() override; private: - std::vector> du_list; + std::unique_ptr odu_hi; std::vector> adaptors; }; diff --git a/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_cli11_schema.cpp b/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_cli11_schema.cpp index 6d8aa8f163..f625a791df 100644 --- a/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_cli11_schema.cpp +++ b/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_cli11_schema.cpp @@ -167,7 +167,7 @@ static void configure_cli11_ru_ofh_base_cell_args(CLI::App& app, ru_ofh_unit_bas ->capture_default_str(); add_option(app, "--iq_scaling", config.iq_scaling, "IQ scaling factor") ->capture_default_str() - ->check(CLI::Range(0.0, 20.0)); + ->check(CLI::Range(0.0, 100.0)); // Callback function for validating that both compression method and bitwidth parameters were specified. auto validate_compression_input = [](CLI::App& cli_app, const std::string& direction) { @@ -227,7 +227,16 @@ static void configure_cli11_ru_ofh_args(CLI::App& app, ru_ofh_unit_parsed_config add_option(app, "--gps_beta", ofh_cfg.gps_Beta, "GPS Beta")->capture_default_str()->check(CLI::Range(-32768, 32767)); // Common cell parameters. - configure_cli11_ru_ofh_base_cell_args(app, config.base_cell_cfg); + auto base_cell_group = app.add_option_group("base_cell"); + configure_cli11_ru_ofh_base_cell_args(*base_cell_group, config.base_cell_cfg); + base_cell_group->parse_complete_callback([&config, &app]() { + for (auto& cell : config.config.cells) { + cell.cell = config.base_cell_cfg; + }; + if (app.get_option("--cells")->get_callback_run()) { + app.get_option("--cells")->run_callback(); + } + }); // Cell parameters. add_option_cell( diff --git a/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_translator.cpp b/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_translator.cpp index ca3f7c020e..1bd54db0e8 100644 --- a/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_translator.cpp +++ b/apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_config_translator.cpp @@ -49,7 +49,7 @@ rx_timing_window_params_us_to_symbols(std::chrono::microseconds T std::chrono::duration symbol_duration) { ofh::rx_window_timing_parameters rx_window_timing_params; - rx_window_timing_params.sym_start = std::ceil(Ta4_min / symbol_duration); + rx_window_timing_params.sym_start = std::floor(Ta4_min / symbol_duration); rx_window_timing_params.sym_end = std::ceil(Ta4_max / symbol_duration); return rx_window_timing_params; diff --git a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.cpp b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.cpp index b66f8ba3fc..94904ce10c 100644 --- a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.cpp +++ b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.cpp @@ -62,11 +62,9 @@ void split_7_2_o_du_application_unit_impl::on_parsing_configuration_registration configure_cli11_with_split_7_2_o_du_unit_config_schema(app, unit_cfg); } -o_du_unit split_7_2_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, - bool use_multicell) +o_du_unit split_7_2_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) { - return use_multicell ? multicell_split_7_2_du_factory(unit_cfg).create_flexible_o_du(dependencies) - : split_7_2_o_du_factory(unit_cfg).create_flexible_o_du(dependencies); + return split_7_2_o_du_factory(unit_cfg).create_flexible_o_du(dependencies); } std::unique_ptr srsran::create_flexible_o_du_application_unit(std::string_view app_name) diff --git a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.h b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.h index 8085255c94..f0771127d3 100644 --- a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.h +++ b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_application_unit_impl.h @@ -46,7 +46,7 @@ class split_7_2_o_du_application_unit_impl : public flexible_o_du_application_un void on_loggers_registration() override; // See interface for documentation. - o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, bool use_multicell = false) override; + o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) override; // See interface for documentation. o_du_high_unit_config& get_o_du_high_unit_config() override { return unit_cfg.odu_high_cfg; } diff --git a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.cpp b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.cpp index d7607dde8d..cedd82b987 100644 --- a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.cpp +++ b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.cpp @@ -31,10 +31,3 @@ split_7_2_o_du_factory::create_radio_unit(const flexible_o_du_ru_config& r { return create_ofh_radio_unit(unit_config.ru_cfg.config, ru_config, ru_dependencies); } - -std::unique_ptr -multicell_split_7_2_du_factory::create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) -{ - return create_ofh_radio_unit(unit_config.ru_cfg.config, ru_config, ru_dependencies); -} diff --git a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.h b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.h index 4ab76e1939..d6ac29ddd5 100644 --- a/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.h +++ b/apps/units/flexible_o_du/split_7_2/split_7_2_o_du_factory.h @@ -23,7 +23,6 @@ #pragma once #include "apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h" -#include "apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.h" #include "split_7_2_o_du_unit_config.h" namespace srsran { @@ -44,20 +43,4 @@ class split_7_2_o_du_factory : public flexible_o_du_factory const flexible_o_du_ru_dependencies& ru_dependencies) override; }; -/// Multicell split 7.2 O-RAN DU factory. -class multicell_split_7_2_du_factory : public multicell_flexible_o_du_factory -{ - const split_7_2_o_du_unit_config& unit_config; - -public: - explicit multicell_split_7_2_du_factory(const split_7_2_o_du_unit_config& config_) : - multicell_flexible_o_du_factory({config_.odu_high_cfg, config_.du_low_cfg}), unit_config(config_) - { - } - -private: - std::unique_ptr create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) override; -}; - } // namespace srsran diff --git a/apps/units/flexible_o_du/split_8/helpers/ru_sdr_config_translator.cpp b/apps/units/flexible_o_du/split_8/helpers/ru_sdr_config_translator.cpp index f6c9b49aee..a5ca1e34b5 100644 --- a/apps/units/flexible_o_du/split_8/helpers/ru_sdr_config_translator.cpp +++ b/apps/units/flexible_o_du/split_8/helpers/ru_sdr_config_translator.cpp @@ -27,14 +27,14 @@ using namespace srsran; -/// Static configuration that the gnb supports. -static constexpr cyclic_prefix cp = cyclic_prefix::NORMAL; - /// Fills the given low PHY configuration from the given gnb configuration. static lower_phy_configuration generate_low_phy_config(const srs_du::du_cell_config& config, const ru_sdr_unit_config& ru_cfg, unsigned max_processing_delay_slot) { + /// Static configuration that the gnb supports. + static constexpr cyclic_prefix cp = cyclic_prefix::NORMAL; + lower_phy_configuration out_cfg; out_cfg.scs = config.scs_common; diff --git a/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.cpp b/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.cpp index a417acb988..7b0fcf8d87 100644 --- a/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.cpp +++ b/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.cpp @@ -62,11 +62,9 @@ void split_8_o_du_application_unit_impl::on_parsing_configuration_registration(C configure_cli11_with_split_8_o_du_unit_config_schema(app, unit_cfg); } -o_du_unit split_8_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, - bool use_multicell) +o_du_unit split_8_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) { - return use_multicell ? multicell_split8_du_factory(unit_cfg).create_flexible_o_du(dependencies) - : split8_du_factory(unit_cfg).create_flexible_o_du(dependencies); + return split8_du_factory(unit_cfg).create_flexible_o_du(dependencies); } std::unique_ptr srsran::create_flexible_o_du_application_unit(std::string_view app_name) diff --git a/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.h b/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.h index 6140644667..57b53d8d75 100644 --- a/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.h +++ b/apps/units/flexible_o_du/split_8/split_8_o_du_application_unit_impl.h @@ -46,7 +46,7 @@ class split_8_o_du_application_unit_impl : public flexible_o_du_application_unit void on_loggers_registration() override; // See interface for documentation. - o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, bool use_multicell = false) override; + o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) override; // See interface for documentation. o_du_high_unit_config& get_o_du_high_unit_config() override { return unit_cfg.odu_high_cfg; } diff --git a/apps/units/flexible_o_du/split_8/split_8_o_du_factory.cpp b/apps/units/flexible_o_du/split_8/split_8_o_du_factory.cpp index 2121794739..9dd1e43d9b 100644 --- a/apps/units/flexible_o_du/split_8/split_8_o_du_factory.cpp +++ b/apps/units/flexible_o_du/split_8/split_8_o_du_factory.cpp @@ -30,10 +30,3 @@ std::unique_ptr split8_du_factory::create_radio_unit(const flexible_ { return create_sdr_radio_unit(unit_config.ru_cfg, ru_config, ru_dependencies); } - -std::unique_ptr -multicell_split8_du_factory::create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) -{ - return create_sdr_radio_unit(unit_config.ru_cfg, ru_config, ru_dependencies); -} diff --git a/apps/units/flexible_o_du/split_8/split_8_o_du_factory.h b/apps/units/flexible_o_du/split_8/split_8_o_du_factory.h index b026fba689..f7638777f1 100644 --- a/apps/units/flexible_o_du/split_8/split_8_o_du_factory.h +++ b/apps/units/flexible_o_du/split_8/split_8_o_du_factory.h @@ -23,7 +23,6 @@ #pragma once #include "apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h" -#include "apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.h" #include "split_8_o_du_unit_config.h" namespace srsran { @@ -44,20 +43,4 @@ class split8_du_factory : public flexible_o_du_factory const flexible_o_du_ru_dependencies& ru_dependencies) override; }; -/// Multicell split 8 O-RAN DU factory. -class multicell_split8_du_factory : public multicell_flexible_o_du_factory -{ - const split_8_o_du_unit_config& unit_config; - -public: - explicit multicell_split8_du_factory(const split_8_o_du_unit_config& config_) : - multicell_flexible_o_du_factory({config_.odu_high_cfg, config_.du_low_cfg}), unit_config(config_) - { - } - -private: - std::unique_ptr create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) override; -}; - } // namespace srsran diff --git a/apps/units/flexible_o_du/split_dynamic/CMakeLists.txt b/apps/units/flexible_o_du/split_dynamic/CMakeLists.txt index 80129b4e33..b09fafeedd 100644 --- a/apps/units/flexible_o_du/split_dynamic/CMakeLists.txt +++ b/apps/units/flexible_o_du/split_dynamic/CMakeLists.txt @@ -24,8 +24,7 @@ set(SOURCES dynamic_o_du_translators.cpp dynamic_o_du_unit_cli11_schema.cpp dynamic_o_du_unit_config_validator.cpp - dynamic_o_du_unit_config_yaml_writer.cpp - multicell_dynamic_o_du_factory.cpp) + dynamic_o_du_unit_config_yaml_writer.cpp) add_library(srsran_flexible_o_du_split_dynamic STATIC ${SOURCES}) target_include_directories(srsran_flexible_o_du_split_dynamic PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.cpp b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.cpp index da3c789276..64c39171b7 100644 --- a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.cpp +++ b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.cpp @@ -21,14 +21,12 @@ */ #include "dynamic_o_du_application_unit_impl.h" -#include "apps/services/e2/e2_metric_connector_manager.h" #include "dynamic_o_du_factory.h" #include "dynamic_o_du_translators.h" #include "dynamic_o_du_unit_cli11_schema.h" #include "dynamic_o_du_unit_config_validator.h" #include "dynamic_o_du_unit_config_yaml_writer.h" #include "dynamic_o_du_unit_logger_registrator.h" -#include "multicell_dynamic_o_du_factory.h" using namespace srsran; @@ -62,11 +60,9 @@ void dynamic_o_du_application_unit_impl::on_parsing_configuration_registration(C configure_cli11_with_dynamic_o_du_unit_config_schema(app, unit_cfg); } -o_du_unit dynamic_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, - bool use_multicell) +o_du_unit dynamic_o_du_application_unit_impl::create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) { - return use_multicell ? multicell_dynamic_o_du_factory(unit_cfg).create_flexible_o_du(dependencies) - : dynamic_o_du_factory(unit_cfg).create_flexible_o_du(dependencies); + return dynamic_o_du_factory(unit_cfg).create_flexible_o_du(dependencies); } std::unique_ptr srsran::create_flexible_o_du_application_unit(std::string_view app_name) diff --git a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.h b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.h index a66a650309..6310f86265 100644 --- a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.h +++ b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_application_unit_impl.h @@ -48,7 +48,7 @@ class dynamic_o_du_application_unit_impl : public flexible_o_du_application_unit void on_loggers_registration() override; // See interface for documentation. - o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies, bool use_multicell = false) override; + o_du_unit create_flexible_o_du_unit(const o_du_unit_dependencies& dependencies) override; // See interface for documentation. o_du_high_unit_config& get_o_du_high_unit_config() override { return unit_cfg.odu_high_cfg; } diff --git a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.cpp b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.cpp index 5b4f05cc00..5b63fbfc76 100644 --- a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.cpp +++ b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.cpp @@ -25,7 +25,6 @@ #include "apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_factories.h" #include "apps/units/flexible_o_du/split_8/helpers/ru_sdr_factories.h" #include "dynamic_o_du_translators.h" -#include "dynamic_o_du_unit_config.h" #include "srsran/ru/ru_dummy_factory.h" using namespace srsran; diff --git a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.h b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.h index 4c36d83827..a96054d293 100644 --- a/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.h +++ b/apps/units/flexible_o_du/split_dynamic/dynamic_o_du_factory.h @@ -22,8 +22,8 @@ #pragma once +#include "apps/units/flexible_o_du/split_dynamic/dynamic_o_du_unit_config.h" #include "apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h" -#include "dynamic_o_du_unit_config.h" namespace srsran { diff --git a/apps/units/flexible_o_du/split_dynamic/multicell_dynamic_o_du_factory.cpp b/apps/units/flexible_o_du/split_dynamic/multicell_dynamic_o_du_factory.cpp deleted file mode 100644 index 66a05fc704..0000000000 --- a/apps/units/flexible_o_du/split_dynamic/multicell_dynamic_o_du_factory.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "multicell_dynamic_o_du_factory.h" -#include "apps/services/worker_manager/worker_manager.h" -#include "apps/units/flexible_o_du/split_7_2/helpers/ru_ofh_factories.h" -#include "apps/units/flexible_o_du/split_8/helpers/ru_sdr_factories.h" -#include "dynamic_o_du_translators.h" -#include "srsran/ru/ru_dummy_factory.h" - -using namespace srsran; - -static std::unique_ptr create_dummy_radio_unit(const ru_dummy_unit_config& ru_cfg, - const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) -{ - ru_dummy_dependencies dependencies; - dependencies.logger = &srslog::fetch_basic_logger("RU"); - dependencies.executor = ru_dependencies.workers.radio_exec; - dependencies.timing_notifier = &ru_dependencies.timing_notifier; - dependencies.symbol_notifier = &ru_dependencies.symbol_notifier; - - return create_dummy_ru( - generate_ru_dummy_config(ru_cfg, ru_config.du_cells, ru_config.max_processing_delay, ru_config.prach_nof_ports), - dependencies); -} - -std::unique_ptr -multicell_dynamic_o_du_factory::create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) -{ - const auto& ru_cfg = unit_config.ru_cfg; - if (const auto* cfg = std::get_if(&ru_cfg)) { - return create_ofh_radio_unit(cfg->config, ru_config, ru_dependencies); - } - - if (const auto* cfg = std::get_if(&ru_cfg)) { - return create_sdr_radio_unit(*cfg, ru_config, ru_dependencies); - } - - return create_dummy_radio_unit(std::get(ru_cfg), ru_config, ru_dependencies); -} diff --git a/apps/units/flexible_o_du/split_helpers/CMakeLists.txt b/apps/units/flexible_o_du/split_helpers/CMakeLists.txt index 678d64cdc0..49b0ff92e7 100644 --- a/apps/units/flexible_o_du/split_helpers/CMakeLists.txt +++ b/apps/units/flexible_o_du/split_helpers/CMakeLists.txt @@ -20,10 +20,7 @@ set(SOURCES flexible_o_du_factory.cpp - flexible_o_du_impl.cpp - multicell_flexible_o_du_factory.cpp - multicell_flexible_o_du_impl.cpp - o_du_high_factory.cpp) + flexible_o_du_impl.cpp) add_library(srsran_flexible_du_helpers STATIC ${SOURCES}) target_include_directories(srsran_flexible_du_helpers PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.cpp b/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.cpp index 689ac53249..e7897a0c3b 100644 --- a/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.cpp +++ b/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.cpp @@ -27,9 +27,7 @@ #include "apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.h" #include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h" #include "apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h" -#include "apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.h" -#include "o_du_high_factory.h" -#include "srsran/du/o_du.h" +#include "flexible_o_du_impl.h" #include "srsran/du/o_du_factory.h" #include "srsran/e2/e2_du_metrics_connector.h" @@ -37,9 +35,9 @@ using namespace srsran; o_du_unit flexible_o_du_factory::create_flexible_o_du(const o_du_unit_dependencies& dependencies) { - o_du_unit odu_unit; - const unsigned nof_cells = config.odu_high_cfg.du_high_cfg.config.cells_cfg.size(); - odu_unit.e2_metric_connectors = std::make_unique< + o_du_unit o_du; + const unsigned nof_cells = config.odu_high_cfg.du_high_cfg.config.cells_cfg.size(); + o_du.e2_metric_connectors = std::make_unique< e2_metric_connector_manager>(nof_cells); const du_high_unit_config& du_hi = config.odu_high_cfg.du_high_cfg.config; @@ -47,8 +45,7 @@ o_du_unit flexible_o_du_factory::create_flexible_o_du(const o_du_unit_dependenci auto du_cells = generate_du_cell_config(du_hi); - std::vector> du_insts; - auto du_impl = std::make_unique(nof_cells); + auto du_impl = std::make_unique(nof_cells); std::vector prach_ports; std::vector max_pusch_per_slot; @@ -57,56 +54,41 @@ o_du_unit flexible_o_du_factory::create_flexible_o_du(const o_du_unit_dependenci max_pusch_per_slot.push_back(high.cell.pusch_cfg.max_puschs_per_slot); } - // O-DU low factory is common for all the cells. + o_du_low_unit_config odu_low_cfg = {du_lo, prach_ports, du_cells, max_pusch_per_slot}; + o_du_low_unit_dependencies odu_low_dependencies = { + du_impl->get_upper_ru_dl_rg_adapter(), du_impl->get_upper_ru_ul_request_adapter(), *dependencies.workers}; o_du_low_unit_factory odu_low_factory(du_lo.hal_config, nof_cells); - - // Create O-DU low. - std::vector du_low_units; + auto odu_lo_unit = odu_low_factory.create(odu_low_cfg, odu_low_dependencies); + + o_du_high_unit_dependencies odu_hi_unit_dependencies = {dependencies.workers->get_du_high_executor_mapper(0), + *dependencies.f1c_client_handler, + *dependencies.f1u_gw, + *dependencies.timer_mng, + *dependencies.mac_p, + *dependencies.rlc_p, + *dependencies.e2_client_handler, + *(o_du.e2_metric_connectors), + *dependencies.json_sink, + *dependencies.metrics_notifier, + {}}; + + // Adjust the dependencies. for (unsigned i = 0, e = du_cells.size(); i != e; ++i) { - o_du_low_unit_config odu_low_cfg = {du_lo, - {prach_ports[i]}, - span(&du_cells[i], 1), - span(&max_pusch_per_slot[i], 1), - i, - nof_cells}; - - o_du_low_unit_dependencies odu_low_dependencies = { - du_impl->get_upper_ru_dl_rg_adapter(), du_impl->get_upper_ru_ul_request_adapter(), *dependencies.workers}; - du_low_units.push_back(odu_low_factory.create(odu_low_cfg, odu_low_dependencies)); + auto& sector_dependencies = odu_hi_unit_dependencies.o_du_hi_dependencies.sectors.emplace_back(); + sector_dependencies.gateway = &odu_lo_unit.o_du_lo->get_slot_message_gateway(i); + sector_dependencies.last_msg_notifier = &odu_lo_unit.o_du_lo->get_slot_last_message_notifier(i); + sector_dependencies.fapi_executor = config.odu_high_cfg.fapi_cfg.l2_nof_slots_ahead != 0 + ? std::optional(dependencies.workers->fapi_exec[i]) + : std::make_optional(); } - // Create O-DU high. - o_du_high_unit_factory_dependencies odu_hi_unit_dependencies = {*dependencies.workers, - *dependencies.f1c_client_handler, - *dependencies.f1u_gw, - *dependencies.timer_mng, - *dependencies.mac_p, - *dependencies.rlc_p, - *dependencies.e2_client_handler, - *(odu_unit.e2_metric_connectors), - *dependencies.json_sink, - *dependencies.metrics_notifier, - {}}; + o_du_high_unit odu_hi_unit = make_o_du_high_unit(config.odu_high_cfg, std::move(odu_hi_unit_dependencies)); - for (unsigned i = 0, e = du_cells.size(); i != e; ++i) { - auto& sector_deps = odu_hi_unit_dependencies.o_du_hi_dependencies.sectors.emplace_back(); - sector_deps.gateway = &du_low_units[i].o_du_lo->get_slot_message_gateway(0); - sector_deps.last_msg_notifier = &du_low_units[i].o_du_lo->get_slot_last_message_notifier(0); - sector_deps.fapi_executor = config.odu_high_cfg.fapi_cfg.l2_nof_slots_ahead != 0 - ? std::optional(dependencies.workers->fapi_exec[i]) - : std::make_optional(); - } - std::vector du_high_units = - make_multicell_with_multi_du(config.odu_high_cfg, std::move(odu_hi_unit_dependencies)); + o_du.metrics = std::move(odu_hi_unit.metrics); + o_du.commands = std::move(odu_hi_unit.commands); - // Create the O-DU. - for (unsigned i = 0, e = du_cells.size(); i != e; ++i) { - du_insts.push_back(srs_du::make_o_du(std::move(du_high_units[i].o_du_hi), std::move(du_low_units[i].o_du_lo))); - } - - // Move commands and metrics. - odu_unit.metrics = std::move(du_high_units.front().metrics); - odu_unit.commands = std::move(du_high_units.front().commands); + auto odu_instance = make_o_du(std::move(odu_hi_unit.o_du_hi), std::move(odu_lo_unit.o_du_lo)); + report_error_if_not(odu_instance, "Invalid Distributed Unit"); flexible_o_du_ru_config ru_config{{du_cells}, du_lo.expert_phy_cfg.max_processing_delay_slots, @@ -117,17 +99,22 @@ o_du_unit flexible_o_du_factory::create_flexible_o_du(const o_du_unit_dependenci du_impl->get_upper_ru_error_adapter()}; std::unique_ptr ru = create_radio_unit(ru_config, ru_dependencies); + srsran_assert(ru, "Invalid Radio Unit"); // Add RU commands. - odu_unit.commands.push_back(std::make_unique()); - odu_unit.commands.push_back(std::make_unique(ru->get_controller())); - odu_unit.commands.push_back(std::make_unique(ru->get_controller())); - odu_unit.commands.push_back(std::make_unique(ru->get_controller())); + o_du.commands.push_back(std::make_unique()); + o_du.commands.push_back(std::make_unique(ru->get_controller())); + o_du.commands.push_back(std::make_unique(ru->get_controller())); + o_du.commands.push_back(std::make_unique(ru->get_controller())); + // Configure the RU and DU in the dynamic DU. du_impl->add_ru(std::move(ru)); - du_impl->add_o_dus(std::move(du_insts)); - odu_unit.unit = std::move(du_impl); + du_impl->add_du(std::move(odu_instance)); + + o_du.unit = std::move(du_impl); + + announce_du_high_cells(du_hi); - return odu_unit; + return o_du; } diff --git a/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h b/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h index 45eeb9c4d5..759f750712 100644 --- a/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h +++ b/apps/units/flexible_o_du/split_helpers/flexible_o_du_factory.h @@ -35,7 +35,6 @@ class flexible_o_du_factory public: explicit flexible_o_du_factory(const flexible_o_du_unit_config& config_) : config(config_) {} - virtual ~flexible_o_du_factory() = default; /// Creates a flexible O-RAN DU using the given dependencies and configuration. diff --git a/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.cpp b/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.cpp index 3eab396177..66107ee86a 100644 --- a/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.cpp +++ b/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.cpp @@ -37,20 +37,14 @@ flexible_o_du_impl::flexible_o_du_impl(unsigned nof_cells) : void flexible_o_du_impl::start() { - for (auto& du_obj : du_list) { - du_obj->get_power_controller().start(); - } - + du->get_power_controller().start(); ru->get_controller().start(); } void flexible_o_du_impl::stop() { ru->get_controller().stop(); - - for (auto& du_obj : du_list) { - du_obj->get_power_controller().stop(); - } + du->get_power_controller().stop(); } void flexible_o_du_impl::add_ru(std::unique_ptr active_ru) @@ -58,22 +52,22 @@ void flexible_o_du_impl::add_ru(std::unique_ptr active_ru) ru = std::move(active_ru); srsran_assert(ru, "Invalid Radio Unit"); + // Connect the RU adaptor to the RU. ru_dl_rg_adapt.connect(ru->get_downlink_plane_handler()); ru_ul_request_adapt.connect(ru->get_uplink_plane_handler()); } -void flexible_o_du_impl::add_o_dus(std::vector> active_o_du) +void flexible_o_du_impl::add_du(std::unique_ptr active_du) { - du_list = std::move(active_o_du); - srsran_assert(!du_list.empty(), "Cannot set an empty DU list"); + du = std::move(active_du); + srsran_assert(du, "Cannot set an invalid DU"); - for (auto& du_obj : du_list) { - span upper_ptrs = du_obj->get_o_du_low().get_du_low().get_all_upper_phys(); - for (auto* upper : upper_ptrs) { - // Make connections between DU and RU. - ru_ul_adapt.map_handler(upper->get_sector_id(), upper->get_rx_symbol_handler()); - ru_timing_adapt.map_handler(upper->get_sector_id(), upper->get_timing_handler()); - ru_error_adapt.map_handler(upper->get_sector_id(), upper->get_error_handler()); - } + // Connect all the sectors of the DU low to the RU adaptors. + span upper_ptrs = du->get_o_du_low().get_du_low().get_all_upper_phys(); + for (auto* upper : upper_ptrs) { + // Make connections between DU and RU. + ru_ul_adapt.map_handler(upper->get_sector_id(), upper->get_rx_symbol_handler()); + ru_timing_adapt.map_handler(upper->get_sector_id(), upper->get_timing_handler()); + ru_error_adapt.map_handler(upper->get_sector_id(), upper->get_error_handler()); } } diff --git a/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.h b/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.h index 328c8ab6b7..8d23a67e0d 100644 --- a/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.h +++ b/apps/units/flexible_o_du/split_helpers/flexible_o_du_impl.h @@ -27,7 +27,6 @@ #include "srsran/du/o_du.h" #include "srsran/ru/ru_adapters.h" #include -#include namespace srsran { @@ -35,7 +34,7 @@ class radio_unit; /// \brief Flexible O-RAN DU implementation. /// -/// The O-RAN DU manages only one cell. To achieve multicell, one O-RAN DU is created per cell. +/// One O-RAN DU can handle more than one cell. class flexible_o_du_impl : public srs_du::du, public du_power_controller { public: @@ -50,11 +49,11 @@ class flexible_o_du_impl : public srs_du::du, public du_power_controller // See interface for documentation. void stop() override; - /// Adds the given RU to this dynamic DU. + /// Adds the given RU to this flexible O-RAN DU. void add_ru(std::unique_ptr active_ru); - /// Adds the given DUs to this dynamic DU. - void add_o_dus(std::vector> active_o_du); + /// Adds the given DU to this flexible O-RAN DU. + void add_du(std::unique_ptr active_du); /// Getters to the adaptors. upper_phy_ru_ul_adapter& get_upper_ru_ul_adapter() { return ru_ul_adapt; } @@ -64,13 +63,13 @@ class flexible_o_du_impl : public srs_du::du, public du_power_controller upper_phy_ru_ul_request_adapter& get_upper_ru_ul_request_adapter() { return ru_ul_request_adapt; } private: - upper_phy_ru_ul_adapter ru_ul_adapt; - upper_phy_ru_timing_adapter ru_timing_adapt; - upper_phy_ru_error_adapter ru_error_adapt; - std::vector> du_list; - std::unique_ptr ru; - upper_phy_ru_dl_rg_adapter ru_dl_rg_adapt; - upper_phy_ru_ul_request_adapter ru_ul_request_adapt; + upper_phy_ru_ul_adapter ru_ul_adapt; + upper_phy_ru_timing_adapter ru_timing_adapt; + upper_phy_ru_error_adapter ru_error_adapt; + std::unique_ptr du; + std::unique_ptr ru; + upper_phy_ru_dl_rg_adapter ru_dl_rg_adapt; + upper_phy_ru_ul_request_adapter ru_ul_request_adapt; }; } // namespace srsran diff --git a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.cpp b/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.cpp deleted file mode 100644 index 53590a902a..0000000000 --- a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "multicell_flexible_o_du_factory.h" -#include "apps/services/e2/e2_metric_connector_manager.h" -#include "apps/services/worker_manager/worker_manager.h" -#include "apps/units/flexible_o_du/flexible_o_du_commands.h" -#include "apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.h" -#include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h" -#include "apps/units/flexible_o_du/o_du_low/o_du_low_unit_factory.h" -#include "multicell_flexible_o_du_impl.h" -#include "srsran/du/o_du_factory.h" -#include "srsran/e2/e2_du_metrics_connector.h" - -using namespace srsran; - -o_du_unit multicell_flexible_o_du_factory::create_flexible_o_du(const o_du_unit_dependencies& dependencies) -{ - o_du_unit o_du; - const unsigned nof_cells = config.odu_high_cfg.du_high_cfg.config.cells_cfg.size(); - o_du.e2_metric_connectors = std::make_unique< - e2_metric_connector_manager>(nof_cells); - - const du_high_unit_config& du_hi = config.odu_high_cfg.du_high_cfg.config; - const du_low_unit_config& du_lo = config.du_low_cfg; - - auto du_cells = generate_du_cell_config(du_hi); - - auto du_impl = std::make_unique(nof_cells); - - std::vector prach_ports; - std::vector max_pusch_per_slot; - for (const auto& high : du_hi.cells_cfg) { - prach_ports.push_back(high.cell.prach_cfg.ports); - max_pusch_per_slot.push_back(high.cell.pusch_cfg.max_puschs_per_slot); - } - - static constexpr unsigned du_id = 0U; - o_du_low_unit_config odu_low_cfg = { - du_lo, prach_ports, du_cells, max_pusch_per_slot, du_id, static_cast(du_cells.size())}; - o_du_low_unit_dependencies odu_low_dependencies = { - du_impl->get_upper_ru_dl_rg_adapter(), du_impl->get_upper_ru_ul_request_adapter(), *dependencies.workers}; - o_du_low_unit_factory odu_low_factory(du_lo.hal_config, nof_cells); - auto odu_lo_unit = odu_low_factory.create(odu_low_cfg, odu_low_dependencies); - - o_du_high_unit_params odu_hi_unit_params = {config.odu_high_cfg, du_id}; - - o_du_high_unit_dependencies odu_hi_unit_dependencies = {dependencies.workers->get_du_high_executor_mapper(du_id), - *dependencies.f1c_client_handler, - *dependencies.f1u_gw, - *dependencies.timer_mng, - *dependencies.mac_p, - *dependencies.rlc_p, - *dependencies.e2_client_handler, - *(o_du.e2_metric_connectors), - *dependencies.json_sink, - *dependencies.metrics_notifier, - {}}; - - // Adjust the dependencies. - for (unsigned i = 0, e = du_cells.size(); i != e; ++i) { - auto& sector_dependencies = odu_hi_unit_dependencies.o_du_hi_dependencies.sectors.emplace_back(); - sector_dependencies.gateway = &odu_lo_unit.o_du_lo->get_slot_message_gateway(i); - sector_dependencies.last_msg_notifier = &odu_lo_unit.o_du_lo->get_slot_last_message_notifier(i); - sector_dependencies.fapi_executor = config.odu_high_cfg.fapi_cfg.l2_nof_slots_ahead != 0 - ? std::optional(dependencies.workers->fapi_exec[i]) - : std::make_optional(); - } - - o_du_high_unit odu_hi_unit = make_o_du_high_unit(odu_hi_unit_params, std::move(odu_hi_unit_dependencies)); - - o_du.metrics = std::move(odu_hi_unit.metrics); - o_du.commands = std::move(odu_hi_unit.commands); - - auto odu_instance = make_o_du(std::move(odu_hi_unit.o_du_hi), std::move(odu_lo_unit.o_du_lo)); - report_error_if_not(odu_instance, "Invalid Distributed Unit"); - - flexible_o_du_ru_config ru_config{{du_cells}, - du_lo.expert_phy_cfg.max_processing_delay_slots, - static_cast(du_hi.cells_cfg.front().cell.prach_cfg.ports.size())}; - flexible_o_du_ru_dependencies ru_dependencies{*dependencies.workers, - du_impl->get_upper_ru_ul_adapter(), - du_impl->get_upper_ru_timing_adapter(), - du_impl->get_upper_ru_error_adapter()}; - - std::unique_ptr ru = create_radio_unit(ru_config, ru_dependencies); - - srsran_assert(ru, "Invalid Radio Unit"); - - // Add RU commands. - o_du.commands.push_back(std::make_unique()); - o_du.commands.push_back(std::make_unique(ru->get_controller())); - o_du.commands.push_back(std::make_unique(ru->get_controller())); - o_du.commands.push_back(std::make_unique(ru->get_controller())); - - // Configure the RU and DU in the dynamic DU. - du_impl->add_ru(std::move(ru)); - du_impl->add_du(std::move(odu_instance)); - - o_du.unit = std::move(du_impl); - - announce_du_high_cells(du_hi); - - return o_du; -} diff --git a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.h b/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.h deleted file mode 100644 index 1fdd3e7728..0000000000 --- a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_factory.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "apps/units/flexible_o_du/o_du_unit.h" -#include "apps/units/flexible_o_du/split_helpers/flexible_o_du_configs.h" -#include "srsran/ru/ru.h" - -namespace srsran { - -/// Multicell flexible O-RAN DU factory interface. -class multicell_flexible_o_du_factory -{ - const flexible_o_du_unit_config config; - -public: - explicit multicell_flexible_o_du_factory(const flexible_o_du_unit_config& config_) : config(config_) {} - virtual ~multicell_flexible_o_du_factory() = default; - - /// Creates a multicell flexible O-RAN DU using the given dependencies and configuration. - o_du_unit create_flexible_o_du(const o_du_unit_dependencies& dependencies); - -private: - /// Creates a Radio Unit using the given config and dependencies. - virtual std::unique_ptr create_radio_unit(const flexible_o_du_ru_config& ru_config, - const flexible_o_du_ru_dependencies& ru_dependencies) = 0; -}; - -} // namespace srsran diff --git a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.cpp b/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.cpp deleted file mode 100644 index c58d8a722b..0000000000 --- a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "multicell_flexible_o_du_impl.h" -#include "srsran/du/du_low/du_low.h" -#include "srsran/du/du_low/o_du_low.h" -#include "srsran/du/o_du.h" -#include "srsran/phy/upper/upper_phy.h" -#include "srsran/ru/ru.h" -#include "srsran/ru/ru_controller.h" - -using namespace srsran; - -multicell_flexible_o_du_impl::multicell_flexible_o_du_impl(unsigned nof_cells) : - ru_ul_adapt(nof_cells), ru_timing_adapt(nof_cells), ru_error_adapt(nof_cells) -{ -} - -void multicell_flexible_o_du_impl::start() -{ - du->get_power_controller().start(); - ru->get_controller().start(); -} - -void multicell_flexible_o_du_impl::stop() -{ - ru->get_controller().stop(); - du->get_power_controller().stop(); -} - -void multicell_flexible_o_du_impl::add_ru(std::unique_ptr active_ru) -{ - ru = std::move(active_ru); - srsran_assert(ru, "Invalid Radio Unit"); - - // Connect the RU adaptor to the RU. - ru_dl_rg_adapt.connect(ru->get_downlink_plane_handler()); - ru_ul_request_adapt.connect(ru->get_uplink_plane_handler()); -} - -void multicell_flexible_o_du_impl::add_du(std::unique_ptr active_du) -{ - du = std::move(active_du); - srsran_assert(du, "Cannot set an invalid DU"); - - // Connect all the sectors of the DU low to the RU adaptors. - span upper_ptrs = du->get_o_du_low().get_du_low().get_all_upper_phys(); - for (auto* upper : upper_ptrs) { - // Make connections between DU and RU. - ru_ul_adapt.map_handler(upper->get_sector_id(), upper->get_rx_symbol_handler()); - ru_timing_adapt.map_handler(upper->get_sector_id(), upper->get_timing_handler()); - ru_error_adapt.map_handler(upper->get_sector_id(), upper->get_error_handler()); - } -} diff --git a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.h b/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.h deleted file mode 100644 index d68fd01164..0000000000 --- a/apps/units/flexible_o_du/split_helpers/multicell_flexible_o_du_impl.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "srsran/du/du.h" -#include "srsran/du/du_power_controller.h" -#include "srsran/du/o_du.h" -#include "srsran/ru/ru_adapters.h" -#include - -namespace srsran { - -class radio_unit; - -/// \brief Multicell flexible O-RAN DU implementation. -/// -/// One O-RAN DU can handle more than one cell. -class multicell_flexible_o_du_impl : public srs_du::du, public du_power_controller -{ -public: - explicit multicell_flexible_o_du_impl(unsigned nof_cells); - - // See interface for documentation. - du_power_controller& get_power_controller() override { return *this; } - - // See interface for documentation. - void start() override; - - // See interface for documentation. - void stop() override; - - /// Adds the given RU to this flexible O-RAN DU. - void add_ru(std::unique_ptr active_ru); - - /// Adds the given DU to this flexible O-RAN DU. - void add_du(std::unique_ptr active_du); - - /// Getters to the adaptors. - upper_phy_ru_ul_adapter& get_upper_ru_ul_adapter() { return ru_ul_adapt; } - upper_phy_ru_timing_adapter& get_upper_ru_timing_adapter() { return ru_timing_adapt; } - upper_phy_ru_error_adapter& get_upper_ru_error_adapter() { return ru_error_adapt; } - upper_phy_ru_dl_rg_adapter& get_upper_ru_dl_rg_adapter() { return ru_dl_rg_adapt; } - upper_phy_ru_ul_request_adapter& get_upper_ru_ul_request_adapter() { return ru_ul_request_adapt; } - -private: - upper_phy_ru_ul_adapter ru_ul_adapt; - upper_phy_ru_timing_adapter ru_timing_adapt; - upper_phy_ru_error_adapter ru_error_adapt; - std::unique_ptr du; - std::unique_ptr ru; - upper_phy_ru_dl_rg_adapter ru_dl_rg_adapt; - upper_phy_ru_ul_request_adapter ru_ul_request_adapt; -}; - -} // namespace srsran diff --git a/apps/units/flexible_o_du/split_helpers/o_du_high_factory.cpp b/apps/units/flexible_o_du/split_helpers/o_du_high_factory.cpp deleted file mode 100644 index e56c625f8c..0000000000 --- a/apps/units/flexible_o_du/split_helpers/o_du_high_factory.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#include "o_du_high_factory.h" -#include "apps/services/worker_manager/worker_manager.h" -#include "apps/units/flexible_o_du/o_du_high/du_high/du_high_config_translators.h" -#include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_config.h" - -using namespace srsran; - -/// \brief Update the Flexible DU metrics configuration with the given local DU configuration and E2 configuration. -/// -/// This function manages the multi cell workaround for the DU high metrics. To have multi cell, now one DU is -/// instantiated per cell, so this would create multiple consumers that does not make sense, for example stdout. -/// With this we avoid having 2 different objects that write in the stdout. -static void update_du_metrics(std::vector& flexible_du_cfg, - std::vector local_du_cfg, - bool is_e2_enabled) -{ - // First call, copy everything. - if (flexible_du_cfg.empty()) { - flexible_du_cfg = std::move(local_du_cfg); - return; - } - - // Safe check that all the DUs provides the same amount of metrics. - srsran_assert(flexible_du_cfg.size() == local_du_cfg.size(), - "Flexible DU metrics size '{}' does not match DU metrics size '{}'", - flexible_du_cfg.size(), - local_du_cfg.size()); - - // Iterate the metrics configs of each DU. Each DU should ha - for (unsigned i = 0, e = local_du_cfg.size(); i != e; ++i) { - // Store the metrics producers for each DU. - flexible_du_cfg[i].producers.push_back(std::move(local_du_cfg[i].producers.back())); - - // Move E2 consumers for each DU to the common output config. E2 Consumers occupy the last position. - if (is_e2_enabled) { - flexible_du_cfg[i].consumers.push_back(std::move(local_du_cfg[i].consumers.back())); - } - } -} - -std::vector srsran::make_multicell_with_multi_du(const o_du_high_unit_config& o_du_high_unit_cfg, - o_du_high_unit_factory_dependencies&& dependencies) -{ - const du_high_unit_config& du_hi = o_du_high_unit_cfg.du_high_cfg.config; - - auto du_cells = generate_du_cell_config(du_hi); - - std::vector du_insts; - - for (unsigned i = 0, e = du_cells.size(); i != e; ++i) { - // Create one DU per cell. - o_du_high_unit_config tmp_cfg; - tmp_cfg.du_high_cfg.config = du_hi; - tmp_cfg.du_high_cfg.config.cells_cfg.resize(1); - tmp_cfg.du_high_cfg.config.cells_cfg[0] = du_hi.cells_cfg[i]; - tmp_cfg.fapi_cfg = o_du_high_unit_cfg.fapi_cfg; - tmp_cfg.e2_cfg = o_du_high_unit_cfg.e2_cfg; - srs_du::o_du_high_dependencies sector_deps; - sector_deps.sectors.push_back(dependencies.o_du_hi_dependencies.sectors[i]); - - o_du_high_unit_params odu_hi_unit_params = {tmp_cfg, i}; - o_du_high_unit_dependencies odu_hi_unit_dependencies = {dependencies.workers.get_du_high_executor_mapper(i), - dependencies.f1c_client_handler, - dependencies.f1u_gw, - dependencies.timer_mng, - dependencies.mac_p, - dependencies.rlc_p, - dependencies.e2_client_handler, - dependencies.e2_metric_connectors, - dependencies.json_sink, - dependencies.metrics_notifier, - sector_deps}; - - du_insts.push_back(make_o_du_high_unit(odu_hi_unit_params, std::move(odu_hi_unit_dependencies))); - - if (i != 0) { - update_du_metrics( - du_insts.front().metrics, std::move(du_insts.back().metrics), tmp_cfg.e2_cfg.base_cfg.enable_unit_e2); - - // Use commands of the first cell only. - du_insts.back().commands.clear(); - } - } - - // Configure the application unit metrics for the DU high. - announce_du_high_cells(du_hi); - - return du_insts; -} diff --git a/apps/units/flexible_o_du/split_helpers/o_du_high_factory.h b/apps/units/flexible_o_du/split_helpers/o_du_high_factory.h deleted file mode 100644 index 7236a0740b..0000000000 --- a/apps/units/flexible_o_du/split_helpers/o_du_high_factory.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * Copyright 2021-2024 Software Radio Systems Limited - * - * This file is part of srsRAN. - * - * srsRAN is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * srsRAN is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * A copy of the GNU Affero General Public License can be found in - * the LICENSE file in the top-level directory of this distribution - * and at http://www.gnu.org/licenses/. - * - */ - -#pragma once - -#include "apps/units/flexible_o_du/o_du_high/o_du_high_unit_factory.h" - -namespace srsran { - -struct worker_manager; - -/// O-RAN DU high unit factory dependencies. -struct o_du_high_unit_factory_dependencies { - worker_manager& workers; - srs_du::f1c_connection_client& f1c_client_handler; - srs_du::f1u_du_gateway& f1u_gw; - timer_manager& timer_mng; - mac_pcap& mac_p; - rlc_pcap& rlc_p; - e2_connection_client& e2_client_handler; - e2_du_metrics_connector_manager& e2_metric_connectors; - srslog::sink& json_sink; - app_services::metrics_notifier& metrics_notifier; - srs_du::o_du_high_dependencies o_du_hi_dependencies; -}; - -/// Creates one O-RAN DU high per cell. -std::vector make_multicell_with_multi_du(const o_du_high_unit_config& o_du_high_unit_cfg, - o_du_high_unit_factory_dependencies&& dependencies); - -} // namespace srsran diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.cpp b/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.cpp index e17d75c9b0..4c991eff45 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.cpp +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.cpp @@ -466,13 +466,14 @@ srs_cu_cp::cu_cp_configuration srsran::generate_cu_cp_config(const cu_cp_unit_co srs_cu_cp::n2_connection_client_config srsran::generate_n2_client_config(bool no_core, const cu_cp_unit_amf_config_item& amf_cfg, dlt_pcap& pcap_writer, - io_broker& broker) + io_broker& broker, + task_executor& io_rx_executor) { using no_core_mode_t = srs_cu_cp::n2_connection_client_config::no_core; using network_mode_t = srs_cu_cp::n2_connection_client_config::network; using ngap_mode_t = std::variant; - ngap_mode_t mode = no_core ? ngap_mode_t{no_core_mode_t{}} : ngap_mode_t{network_mode_t{broker}}; + ngap_mode_t mode = no_core ? ngap_mode_t{no_core_mode_t{}} : ngap_mode_t{network_mode_t{broker, io_rx_executor}}; if (not no_core) { network_mode_t& nw_mode = std::get(mode); nw_mode.amf_address = amf_cfg.ip_addr; diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.h b/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.h index 6e9810f38d..9f35a9a698 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.h +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_config_translators.h @@ -38,7 +38,8 @@ srs_cu_cp::cu_cp_configuration generate_cu_cp_config(const cu_cp_unit_config& cu srs_cu_cp::n2_connection_client_config generate_n2_client_config(bool no_core, const cu_cp_unit_amf_config_item& amf_cfg, dlt_pcap& pcap_writer, - io_broker& broker); + io_broker& broker, + task_executor& io_rx_executor); /// Fills the CU-CP worker manager parameters of the given worker manager configuration. void fill_cu_cp_worker_manager_config(worker_manager_config& config, const cu_cp_unit_config& unit_cfg); diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_logger_registrator.h b/apps/units/o_cu_cp/cu_cp/cu_cp_logger_registrator.h index b2553cb5ef..20af09d555 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_logger_registrator.h +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_logger_registrator.h @@ -52,6 +52,10 @@ inline void register_cu_cp_loggers(const cu_cp_unit_logger_config& log_cfg) ngap_logger.set_level(log_cfg.ngap_level); ngap_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + auto& nrppa_logger = srslog::fetch_basic_logger("NRPPA", false); + nrppa_logger.set_level(log_cfg.nrppa_level); + nrppa_logger.set_hex_dump_max_size(log_cfg.hex_max_size); + auto& sec_logger = srslog::fetch_basic_logger("SEC", false); sec_logger.set_level(log_cfg.sec_level); sec_logger.set_hex_dump_max_size(log_cfg.hex_max_size); diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config.h b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config.h index 0147fde926..e247ef5333 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config.h +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config.h @@ -29,6 +29,7 @@ #include "srsran/ran/pci.h" #include "srsran/ran/qos/five_qi.h" #include "srsran/ran/s_nssai.h" +#include "srsran/ran/tac.h" #include namespace srsran { @@ -45,7 +46,7 @@ struct cu_cp_unit_plmn_item { }; struct cu_cp_unit_supported_ta_item { - unsigned tac; + tac_t tac; std::vector plmn_list; }; diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_cli11_schema.cpp b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_cli11_schema.cpp index 50fd40a27b..2c3edbbe4f 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_cli11_schema.cpp +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_cli11_schema.cpp @@ -34,6 +34,7 @@ static void configure_cli11_log_args(CLI::App& app, cu_cp_unit_logger_config& lo app_services::add_log_option(app, log_params.pdcp_level, "--pdcp_level", "PDCP log level"); app_services::add_log_option(app, log_params.rrc_level, "--rrc_level", "RRC log level"); app_services::add_log_option(app, log_params.ngap_level, "--ngap_level", "NGAP log level"); + app_services::add_log_option(app, log_params.nrppa_level, "--nrppa_level", "NRPPA log level")->group(""); app_services::add_log_option(app, log_params.f1ap_level, "--f1ap_level", "F1AP log level"); app_services::add_log_option(app, log_params.cu_level, "--cu_level", "Log level for the CU"); app_services::add_log_option(app, log_params.sec_level, "--sec_level", "Security functions log level"); diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_validator.cpp b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_validator.cpp index 25dbdc4a75..8844cba9dd 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_validator.cpp +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_validator.cpp @@ -35,14 +35,14 @@ static bool validate_mobility_appconfig(gnb_id_t gnb_id, const cu_cp_unit_mobili { std::map report_cfg_ids_to_report_type; for (const auto& report_cfg : config.report_configs) { - // check that report config ids are unique + // Check that report config ids are unique. if (report_cfg_ids_to_report_type.find(report_cfg.report_cfg_id) != report_cfg_ids_to_report_type.end()) { fmt::print("Report config ids must be unique\n"); return false; } report_cfg_ids_to_report_type.emplace(report_cfg.report_cfg_id, report_cfg.report_type); - // check that report configs are valid + // Check that report configs are valid. if (report_cfg.report_type == "event_triggered") { if (!report_cfg.event_triggered_report_type.has_value()) { fmt::print("Invalid CU-CP configuration. If report type is set to \"event_triggered\" then " @@ -82,7 +82,7 @@ static bool validate_mobility_appconfig(gnb_id_t gnb_id, const cu_cp_unit_mobili std::map> cell_to_report_cfg_id; - // check cu_cp_cell_config + // Check cu_cp_cell_config. std::set ncis; for (const auto& cell : config.cells) { nr_cell_identity nci = nr_cell_identity::create(cell.nr_cell_id).value(); @@ -98,23 +98,23 @@ static bool validate_mobility_appconfig(gnb_id_t gnb_id, const cu_cp_unit_mobili } if (cell.periodic_report_cfg_id.has_value()) { - // try to add report config id to cell_to_report_cfg_id map + // Try to add report config id to cell_to_report_cfg_id map. cell_to_report_cfg_id.emplace(nci, std::set()); auto& report_cfg_ids = cell_to_report_cfg_id.at(nci); if (!report_cfg_ids.emplace(cell.periodic_report_cfg_id.value()).second) { - fmt::print("cell={}: report_config_id={} already configured for this cell)\n", + fmt::print("cell={:#x}: report_config_id={} already configured for this cell)\n", cell.nr_cell_id, cell.periodic_report_cfg_id.value()); return false; } - // check that for the serving cell only periodic reports are configured + // Check that for the serving cell only periodic reports are configured. if (report_cfg_ids_to_report_type.at(cell.periodic_report_cfg_id.value()) != "periodical") { fmt::print("For the serving cell only periodic reports are allowed\n"); return false; } } - // check if cell is an external managed cell + // Check if cell is an external managed cell. if (nci.gnb_id(gnb_id.bit_length) != gnb_id) { if (!cell.gnb_id_bit_length.has_value() || !cell.pci.has_value() || !cell.band.has_value() || !cell.ssb_arfcn.has_value() || !cell.ssb_scs.has_value() || !cell.ssb_period.has_value() || @@ -137,23 +137,21 @@ static bool validate_mobility_appconfig(gnb_id_t gnb_id, const cu_cp_unit_mobili } } - // check that for neighbor cells managed by this CU-CP no periodic reports are configured + // Check that for neighbor cells managed by this CU-CP no periodic reports are configured. for (const auto& ncell : cell.ncells) { - // try to add report config ids to cell_to_report_cfg_id map for (const auto& id : ncell.report_cfg_ids) { - if (cell_to_report_cfg_id.find(nci) != cell_to_report_cfg_id.end() && - !cell_to_report_cfg_id.at(nci).emplace(id).second) { - fmt::print("cell={}: report_config_id={} already configured for this cell\n", ncell.nr_cell_id, id); + if (report_cfg_ids_to_report_type.at(id) == "periodical") { + fmt::print("cell={:#x}: For neighbor cells no periodic reports are allowed\n", cell.nr_cell_id); return false; } } } } - // verify that each configured neighbor cell is present + // Verify that each configured neighbor cell is present. for (const auto& cell : config.cells) { - nr_cell_identity nci = nr_cell_identity::create(cell.nr_cell_id).value(); for (const auto& ncell : cell.ncells) { + nr_cell_identity nci = nr_cell_identity::create(ncell.nr_cell_id).value(); if (ncis.find(nci) == ncis.end()) { fmt::print("Neighbor cell config for nci={:#x} incomplete. No valid configuration for cell nci={:#x} found.\n", cell.nr_cell_id, diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_yaml_writer.cpp b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_yaml_writer.cpp index 9c595c9e3e..0142118136 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_yaml_writer.cpp +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_config_yaml_writer.cpp @@ -267,6 +267,7 @@ static void fill_cu_cp_log_section(YAML::Node node, const cu_cp_unit_logger_conf node["pdcp_level"] = srslog::basic_level_to_string(config.pdcp_level); node["rrc_level"] = srslog::basic_level_to_string(config.rrc_level); node["ngap_level"] = srslog::basic_level_to_string(config.ngap_level); + node["nrppa_level"] = srslog::basic_level_to_string(config.nrppa_level); node["f1ap_level"] = srslog::basic_level_to_string(config.f1ap_level); node["cu_level"] = srslog::basic_level_to_string(config.cu_level); node["sec_level"] = srslog::basic_level_to_string(config.sec_level); diff --git a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_logger_config.h b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_logger_config.h index f3f8683ca3..70ecaa0fae 100644 --- a/apps/units/o_cu_cp/cu_cp/cu_cp_unit_logger_config.h +++ b/apps/units/o_cu_cp/cu_cp/cu_cp_unit_logger_config.h @@ -28,12 +28,13 @@ namespace srsran { /// Configuration of logging functionalities. struct cu_cp_unit_logger_config { - srslog::basic_levels cu_level = srslog::basic_levels::warning; - srslog::basic_levels f1ap_level = srslog::basic_levels::warning; - srslog::basic_levels pdcp_level = srslog::basic_levels::warning; - srslog::basic_levels rrc_level = srslog::basic_levels::warning; - srslog::basic_levels ngap_level = srslog::basic_levels::warning; - srslog::basic_levels sec_level = srslog::basic_levels::warning; + srslog::basic_levels cu_level = srslog::basic_levels::warning; + srslog::basic_levels f1ap_level = srslog::basic_levels::warning; + srslog::basic_levels pdcp_level = srslog::basic_levels::warning; + srslog::basic_levels rrc_level = srslog::basic_levels::warning; + srslog::basic_levels ngap_level = srslog::basic_levels::warning; + srslog::basic_levels nrppa_level = srslog::basic_levels::warning; + srslog::basic_levels sec_level = srslog::basic_levels::warning; /// Maximum number of bytes to write when dumping hex arrays. int hex_max_size = 0; /// Enable JSON generation for the F1AP Tx and Rx PDUs. diff --git a/apps/units/o_cu_cp/o_cu_cp_builder.cpp b/apps/units/o_cu_cp/o_cu_cp_builder.cpp index cb6bea858c..e54c0c520f 100644 --- a/apps/units/o_cu_cp/o_cu_cp_builder.cpp +++ b/apps/units/o_cu_cp/o_cu_cp_builder.cpp @@ -36,6 +36,7 @@ using namespace srsran; o_cu_cp_unit srsran::build_o_cu_cp(const o_cu_cp_unit_config& unit_cfg, o_cu_cp_unit_dependencies& dependencies) { srsran_assert(dependencies.cu_cp_executor, "Invalid CU-CP executor"); + srsran_assert(dependencies.cu_cp_n2_rx_executor, "Invalid N2 Rx executor"); srsran_assert(dependencies.cu_cp_e2_exec, "Invalid E2 executor"); srsran_assert(dependencies.ngap_pcap, "Invalid NGAP PCAP"); srsran_assert(dependencies.broker, "Invalid IO broker"); @@ -52,16 +53,24 @@ o_cu_cp_unit srsran::build_o_cu_cp(const o_cu_cp_unit_config& unit_cfg, o_cu_cp_ // Create N2 Client Gateways. std::vector> n2_clients; - n2_clients.push_back(srs_cu_cp::create_n2_connection_client(generate_n2_client_config( - cucp_unit_cfg.amf_config.no_core, cucp_unit_cfg.amf_config.amf, *dependencies.ngap_pcap, *dependencies.broker))); + n2_clients.push_back( + srs_cu_cp::create_n2_connection_client(generate_n2_client_config(cucp_unit_cfg.amf_config.no_core, + cucp_unit_cfg.amf_config.amf, + *dependencies.ngap_pcap, + *dependencies.broker, + *dependencies.cu_cp_n2_rx_executor))); for (const auto& amf : cucp_unit_cfg.extra_amfs) { - n2_clients.push_back(srs_cu_cp::create_n2_connection_client(generate_n2_client_config( - cucp_unit_cfg.amf_config.no_core, amf, *dependencies.ngap_pcap, *dependencies.broker))); + n2_clients.push_back( + srs_cu_cp::create_n2_connection_client(generate_n2_client_config(cucp_unit_cfg.amf_config.no_core, + amf, + *dependencies.ngap_pcap, + *dependencies.broker, + *dependencies.cu_cp_n2_rx_executor))); } - for (unsigned pos = 0; pos < n2_clients.size(); pos++) { - cu_cp_cfg.ngaps[pos].n2_gw = n2_clients[pos].get(); + for (unsigned i = 0, e = n2_clients.size(); i != e; ++i) { + cu_cp_cfg.ngaps[i].n2_gw = n2_clients[i].get(); } auto e2_metric_connectors = std::make_unique(); diff --git a/apps/units/o_cu_cp/o_cu_cp_builder.h b/apps/units/o_cu_cp/o_cu_cp_builder.h index 0dab89216e..984bd9555e 100644 --- a/apps/units/o_cu_cp/o_cu_cp_builder.h +++ b/apps/units/o_cu_cp/o_cu_cp_builder.h @@ -50,13 +50,14 @@ class e2_gateway_remote_connector; /// O-RAN CU-CP build dependencies. struct o_cu_cp_unit_dependencies { - task_executor* cu_cp_executor = nullptr; - task_executor* cu_cp_e2_exec = nullptr; - timer_manager* timers = nullptr; - dlt_pcap* ngap_pcap = nullptr; - io_broker* broker = nullptr; - e2_connection_client* e2_gw = nullptr; - app_services::metrics_notifier* metrics_notifier = nullptr; + task_executor* cu_cp_executor = nullptr; + task_executor* cu_cp_n2_rx_executor = nullptr; + task_executor* cu_cp_e2_exec = nullptr; + timer_manager* timers = nullptr; + dlt_pcap* ngap_pcap = nullptr; + io_broker* broker = nullptr; + e2_connection_client* e2_gw = nullptr; + app_services::metrics_notifier* metrics_notifier = nullptr; }; /// O-RAN CU-CP unit. diff --git a/apps/units/o_cu_up/cu_up/cu_up_unit_config.h b/apps/units/o_cu_up/cu_up/cu_up_unit_config.h index 2a8e03d846..52cda72b4b 100644 --- a/apps/units/o_cu_up/cu_up/cu_up_unit_config.h +++ b/apps/units/o_cu_up/cu_up/cu_up_unit_config.h @@ -22,6 +22,7 @@ #pragma once +#include "apps/services/network/udp_cli11_schema.h" #include "apps/units/o_cu_up/cu_up/cu_up_unit_pcap_config.h" #include "cu_up_unit_logger_config.h" #include "srsran/ran/qos/five_qi.h" @@ -37,13 +38,16 @@ struct cu_up_unit_metrics_config { } pdcp; }; -struct cu_up_unit_upf_config { - std::string bind_addr = "auto"; - std::string bind_interface = "auto"; - std::string ext_addr = "auto"; - int udp_rx_max_msgs = 256; - float pool_threshold = 0.9; - bool no_core = false; +struct cu_up_unit_ngu_socket_config { + std::string bind_addr = "auto"; + std::string bind_interface = "auto"; + std::string ext_addr = "auto"; + udp_appconfig udp_config = {}; +}; + +struct cu_up_unit_ngu_config { + bool no_core = false; + std::vector ngu_socket_cfg; }; /// F1-U configuration at CU_UP side @@ -72,7 +76,7 @@ struct cu_up_unit_config { unsigned gtpu_reordering_timer_ms = 0; bool warn_on_drop = false; /// UPF configuration. - cu_up_unit_upf_config upf_cfg; + cu_up_unit_ngu_config ngu_cfg; /// Metrics. cu_up_unit_metrics_config metrics; /// Loggers. diff --git a/apps/units/o_cu_up/cu_up/cu_up_unit_config_cli11_schema.cpp b/apps/units/o_cu_up/cu_up/cu_up_unit_config_cli11_schema.cpp index 3b9e26f229..46c3d019bd 100644 --- a/apps/units/o_cu_up/cu_up/cu_up_unit_config_cli11_schema.cpp +++ b/apps/units/o_cu_up/cu_up/cu_up_unit_config_cli11_schema.cpp @@ -29,29 +29,65 @@ using namespace srsran; -static void configure_cli11_upf_args(CLI::App& app, cu_up_unit_upf_config& upf_params) +static void configure_cli11_ngu_socket_args(CLI::App& app, cu_up_unit_ngu_socket_config& ngu_sock_params) { - add_option(app, "--bind_addr", upf_params.bind_addr, "Local IP address to bind for N3 interface") + add_option(app, "--bind_addr", ngu_sock_params.bind_addr, "Local IP address to bind for N3 interface") ->check(CLI::ValidIPV4 | CLI::IsMember({"auto"})); - add_option(app, "--bind_interface", upf_params.bind_interface, "Network device to bind for N3 interface") + add_option(app, "--bind_interface", ngu_sock_params.bind_interface, "Network device to bind for N3 interface") ->capture_default_str(); add_option(app, "--ext_addr", - upf_params.ext_addr, + ngu_sock_params.ext_addr, "External IP address that is advertised to receive GTP-U packets from UPF via N3 interface") ->check(CLI::ValidIPV4 | CLI::IsMember({"auto"})); - add_option(app, "--udp_max_rx_msgs", upf_params.udp_rx_max_msgs, "Maximum amount of messages RX in a single syscall"); - add_option( - app, "--pool_threshold", upf_params.pool_threshold, "Pool accupancy threshold after which packets are dropped") - ->check(CLI::Range(0.0, 1.0)); - add_option(app, "--no_core", upf_params.no_core, "Allow gNB to run without a core"); + + configure_cli11_with_udp_config_schema(app, ngu_sock_params.udp_config); +} + +static void configure_cli11_ngu_args(CLI::App& app, cu_up_unit_ngu_config& ngu_params) +{ + add_option(app, "--no_core", ngu_params.no_core, "Allow gNB to run without a core"); + + // Add option for multiple sockets, for usage with different slices, 5QIs or parallization. + auto sock_lambda = [&ngu_params](const std::vector& values) { + // Prepare the radio bearers + ngu_params.ngu_socket_cfg.resize(values.size()); + + // Format every QoS setting. + for (unsigned i = 0, e = values.size(); i != e; ++i) { + CLI::App subapp("NG-U socket parameters", "NG-U socket config, item #" + std::to_string(i)); + subapp.config_formatter(create_yaml_config_parser()); + subapp.allow_config_extras(CLI::config_extras_mode::capture); + configure_cli11_ngu_socket_args(subapp, ngu_params.ngu_socket_cfg[i]); + std::istringstream ss(values[i]); + subapp.parse_from_stream(ss); + } + }; + add_option_cell(app, "--socket", sock_lambda, "Configures UDP/IP socket parameters of the N3 interface"); +} + +static void configure_cli11_test_mode_args(CLI::App& app, cu_up_unit_test_mode_config& test_mode_params) +{ + add_option(app, "--enable", test_mode_params.enabled, "Enable or disable CU-UP test mode"); + add_option(app, "--integrity_enable", test_mode_params.integrity_enabled, "Enable or disable PDCP integrity testing"); + add_option(app, "--ciphering_enable", test_mode_params.ciphering_enabled, "Enable or disable PDCP ciphering testing"); + add_option(app, "--nea_algo", test_mode_params.nea_algo, "NEA algo to use for testing. Valid values {0, 1, 2, 3}.") + ->capture_default_str() + ->check(CLI::Range(0, 3)); + add_option(app, "--nia_algo", test_mode_params.nea_algo, "NIA algo to use for testing. Valid values {1, 2, 3}.") + ->capture_default_str() + ->check(CLI::Range(1, 3)); } static void configure_cli11_cu_up_args(CLI::App& app, cu_up_unit_config& cu_up_params) { // UPF section. - CLI::App* upf_subcmd = add_subcommand(app, "upf", "UPF parameters")->configurable(); - configure_cli11_upf_args(*upf_subcmd, cu_up_params.upf_cfg); + CLI::App* ngu_subcmd = add_subcommand(app, "ngu", "NG-U parameters")->configurable(); + configure_cli11_ngu_args(*ngu_subcmd, cu_up_params.ngu_cfg); + + // Test mode section. + CLI::App* test_mode_subcmd = add_subcommand(app, "test_mode", "CU-UP test mode parameters")->configurable(); + configure_cli11_test_mode_args(*test_mode_subcmd, cu_up_params.test_mode_cfg); add_option(app, "--gtpu_queue_size", cu_up_params.gtpu_queue_size, "GTP-U queue size, in PDUs") ->capture_default_str(); @@ -118,19 +154,6 @@ static void configure_cli11_qos_args(CLI::App& app, cu_up_unit_qos_config& qos_p configure_cli11_f1u_cu_up_args(*f1u_cu_up_subcmd, qos_params.f1u_cu_up); } -static void configure_cli11_test_mode_args(CLI::App& app, cu_up_unit_test_mode_config& test_mode_params) -{ - add_option(app, "--enable", test_mode_params.enabled, "Enable or disable CU-UP test mode"); - add_option(app, "--integrity_enable", test_mode_params.integrity_enabled, "Enable or disable PDCP integrity testing"); - add_option(app, "--ciphering_enable", test_mode_params.ciphering_enabled, "Enable or disable PDCP ciphering testing"); - add_option(app, "--nea_algo", test_mode_params.nea_algo, "NEA algo to use for testing. Valid values {0, 1, 2, 3}.") - ->capture_default_str() - ->check(CLI::Range(0, 3)); - add_option(app, "--nia_algo", test_mode_params.nea_algo, "NIA algo to use for testing. Valid values {1, 2, 3}.") - ->capture_default_str() - ->check(CLI::Range(1, 3)); -} - void srsran::configure_cli11_with_cu_up_unit_config_schema(CLI::App& app, cu_up_unit_config& unit_cfg) { // CU-UP section. @@ -165,8 +188,4 @@ void srsran::configure_cli11_with_cu_up_unit_config_schema(CLI::App& app, cu_up_ } }; add_option_cell(app, "--qos", qos_lambda, "Configures RLC and PDCP radio bearers on a per 5QI basis."); - - // Test mode section. - CLI::App* test_mode_subcmd = add_subcommand(app, "test_mode", "CU-UP test mode parameters")->configurable(); - configure_cli11_test_mode_args(*test_mode_subcmd, unit_cfg.test_mode_cfg); } diff --git a/apps/units/o_cu_up/cu_up/cu_up_unit_config_translators.cpp b/apps/units/o_cu_up/cu_up/cu_up_unit_config_translators.cpp index 3407415e6f..c421d3e96a 100644 --- a/apps/units/o_cu_up/cu_up/cu_up_unit_config_translators.cpp +++ b/apps/units/o_cu_up/cu_up/cu_up_unit_config_translators.cpp @@ -35,12 +35,6 @@ srs_cu_up::cu_up_config srsran::generate_cu_up_config(const cu_up_unit_config& c out_cfg.n3_cfg.gtpu_reordering_timer = std::chrono::milliseconds{config.gtpu_reordering_timer_ms}; out_cfg.n3_cfg.warn_on_drop = config.warn_on_drop; - out_cfg.net_cfg.n3_bind_addr = config.upf_cfg.bind_addr; - out_cfg.net_cfg.n3_ext_addr = config.upf_cfg.ext_addr; - out_cfg.net_cfg.n3_bind_interface = config.upf_cfg.bind_interface; - out_cfg.net_cfg.n3_rx_max_mmsg = config.upf_cfg.udp_rx_max_msgs; - out_cfg.net_cfg.pool_threshold = config.upf_cfg.pool_threshold; - out_cfg.test_mode_cfg.enabled = config.test_mode_cfg.enabled; out_cfg.test_mode_cfg.integrity_enabled = config.test_mode_cfg.integrity_enabled; return out_cfg; diff --git a/apps/units/o_cu_up/cu_up/cu_up_unit_config_yaml_writer.cpp b/apps/units/o_cu_up/cu_up/cu_up_unit_config_yaml_writer.cpp index b465eae524..53ea5fbb37 100644 --- a/apps/units/o_cu_up/cu_up/cu_up_unit_config_yaml_writer.cpp +++ b/apps/units/o_cu_up/cu_up/cu_up_unit_config_yaml_writer.cpp @@ -26,13 +26,35 @@ using namespace srsran; -static void fill_cu_up_upf_section(YAML::Node node, const cu_up_unit_upf_config& config) +static void fill_cu_up_udp_section(YAML::Node node, const udp_appconfig& config) { - node["bind_addr"] = config.bind_addr; - node["bind_interface"] = config.bind_interface; - node["ext_addr"] = config.ext_addr; - node["udp_max_rx_msgs"] = config.udp_rx_max_msgs; - node["no_core"] = config.no_core; + node["max_rx_msgs"] = config.rx_max_msgs; + node["pool_threshold"] = config.pool_threshold; +} + +static void fill_cu_up_ngu_socket_entry(YAML::Node& node, const cu_up_unit_ngu_socket_config& config) +{ + node["bind_addr"] = config.bind_addr; + node["bind_interface"] = config.bind_interface; + node["ext_addr"] = config.ext_addr; + fill_cu_up_udp_section(node["udp"], config.udp_config); +} + +static void fill_cu_up_ngu_socket_section(YAML::Node node, const std::vector& sock_cfg) +{ + auto sock_node = node["socket"]; + for (const auto& cfg : sock_cfg) { + YAML::Node node_sock; + fill_cu_up_ngu_socket_entry(node_sock, cfg); + sock_node.push_back(node_sock); + } +} + +static void fill_cu_up_ngu_section(YAML::Node node, const cu_up_unit_ngu_config& config) +{ + node["no_core"] = config.no_core; + + fill_cu_up_ngu_socket_section(node, config.ngu_socket_cfg); } static void fill_cu_up_metrics_section(YAML::Node node, const cu_up_unit_metrics_config& config) @@ -114,7 +136,7 @@ void srsran::fill_cu_up_config_in_yaml_schema(YAML::Node& node, const cu_up_unit fill_cu_up_log_section(node["log"], config.loggers); fill_cu_up_pcap_section(node["pcap"], config.pcap_cfg); fill_cu_up_metrics_section(node["metrics"], config.metrics); - fill_cu_up_upf_section(node["upf"], config.upf_cfg); + fill_cu_up_ngu_section(node["ngu"], config.ngu_cfg); fill_cu_up_qos_section(node, config.qos_cfg); } diff --git a/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.cpp b/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.cpp index 3928ede7e8..aeaf72f990 100644 --- a/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.cpp +++ b/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.cpp @@ -42,3 +42,9 @@ void srsran::configure_cli11_with_o_cu_up_e2_config_schema(CLI::App& app, o_cu_u CLI::App* pcap_subcmd = add_subcommand(app, "pcap", "Logging configuration")->configurable(); configure_cli11_pcap_args(*pcap_subcmd, unit_cfg.pcaps); } + +void srsran::autoderive_o_cu_up_e2_parameters_after_parsing(o_cu_up_e2_config& unit_cfg) +{ + // If CU UP E2 agent is disabled do not enable e2ap pcap for it. + unit_cfg.pcaps.enabled = unit_cfg.base_config.enable_unit_e2 && unit_cfg.pcaps.enabled; +} \ No newline at end of file diff --git a/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.h b/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.h index a25179192d..b27175e882 100644 --- a/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.h +++ b/apps/units/o_cu_up/e2/o_cu_up_e2_config_cli11_schema.h @@ -31,4 +31,7 @@ struct o_cu_up_e2_config; /// Configures the given CLI11 application with the O-RAN CU-UP application unit E2 configuration schema. void configure_cli11_with_o_cu_up_e2_config_schema(CLI::App& app, o_cu_up_e2_config& unit_cfg); +/// Auto derive O-RAN CU-UP E2 parameters after the parsing. +void autoderive_o_cu_up_e2_parameters_after_parsing(o_cu_up_e2_config& unit_cfg); + } // namespace srsran diff --git a/apps/units/o_cu_up/o_cu_up_application_unit_impl.cpp b/apps/units/o_cu_up/o_cu_up_application_unit_impl.cpp index 06408739b3..7b9c593e0a 100644 --- a/apps/units/o_cu_up/o_cu_up_application_unit_impl.cpp +++ b/apps/units/o_cu_up/o_cu_up_application_unit_impl.cpp @@ -46,6 +46,11 @@ void o_cu_up_application_unit_impl::on_parsing_configuration_registration(CLI::A configure_cli11_with_o_cu_up_e2_config_schema(app, unit_cfg.e2_cfg); } +void o_cu_up_application_unit_impl::on_configuration_parameters_autoderivation(CLI::App& app) +{ + autoderive_o_cu_up_e2_parameters_after_parsing(unit_cfg.e2_cfg); +} + bool o_cu_up_application_unit_impl::on_configuration_validation(const os_sched_affinity_bitmask& available_cpus) const { return validate_cu_up_unit_config(unit_cfg.cu_up_cfg); diff --git a/apps/units/o_cu_up/o_cu_up_application_unit_impl.h b/apps/units/o_cu_up/o_cu_up_application_unit_impl.h index 43a34d6b7f..27a0eea05d 100644 --- a/apps/units/o_cu_up/o_cu_up_application_unit_impl.h +++ b/apps/units/o_cu_up/o_cu_up_application_unit_impl.h @@ -37,7 +37,7 @@ class o_cu_up_application_unit_impl : public o_cu_up_application_unit void on_parsing_configuration_registration(CLI::App& app) override; // See interface for documentation. - void on_configuration_parameters_autoderivation(CLI::App& app) override {} + void on_configuration_parameters_autoderivation(CLI::App& app) override; // See interface for documentation. bool on_configuration_validation(const os_sched_affinity_bitmask& available_cpus) const override; diff --git a/apps/units/o_cu_up/o_cu_up_builder.cpp b/apps/units/o_cu_up/o_cu_up_builder.cpp index aa95b03e68..dd9e8e9321 100644 --- a/apps/units/o_cu_up/o_cu_up_builder.cpp +++ b/apps/units/o_cu_up/o_cu_up_builder.cpp @@ -74,21 +74,37 @@ o_cu_up_unit srsran::build_o_cu_up(const o_cu_up_unit_config& unit_cfg, const o_ auto address = dependencies.f1u_gateway->get_cu_bind_address(); srsran_assert(address.has_value(), "Invalid F1-U bind address"); - config.cu_up_cfg.net_cfg.f1u_bind_addr = address.value(); - // Create NG-U gateway. - std::unique_ptr ngu_gw; - if (not unit_cfg.cu_up_cfg.upf_cfg.no_core) { - udp_network_gateway_config ngu_gw_config = {}; - ngu_gw_config.bind_address = config.cu_up_cfg.net_cfg.n3_bind_addr; - ngu_gw_config.bind_port = config.cu_up_cfg.net_cfg.n3_bind_port; - ngu_gw_config.bind_interface = config.cu_up_cfg.net_cfg.n3_bind_interface; - ngu_gw_config.rx_max_mmsg = config.cu_up_cfg.net_cfg.n3_rx_max_mmsg; - ngu_gw = srs_cu_up::create_udp_ngu_gateway( - ngu_gw_config, *dependencies.io_brk, dependencies.workers->cu_up_exec_mapper->io_ul_executor()); + // Create NG-U gateway(s). + std::vector> ngu_gws; + if (not unit_cfg.cu_up_cfg.ngu_cfg.no_core) { + for (const cu_up_unit_ngu_socket_config& sock_cfg : unit_cfg.cu_up_cfg.ngu_cfg.ngu_socket_cfg) { + udp_network_gateway_config n3_udp_cfg = {}; + n3_udp_cfg.bind_address = sock_cfg.bind_addr; + n3_udp_cfg.bind_interface = sock_cfg.bind_interface; + n3_udp_cfg.pool_occupancy_threshold = sock_cfg.udp_config.pool_threshold; + n3_udp_cfg.bind_port = GTPU_PORT; + n3_udp_cfg.rx_max_mmsg = sock_cfg.udp_config.rx_max_msgs; + n3_udp_cfg.pool_occupancy_threshold = sock_cfg.udp_config.pool_threshold; + n3_udp_cfg.reuse_addr = false; // TODO allow reuse_addr for multiple sockets + n3_udp_cfg.dscp = sock_cfg.udp_config.dscp; + + std::unique_ptr ngu_gw = + create_udp_gtpu_gateway(n3_udp_cfg, + *dependencies.io_brk, + dependencies.workers->cu_up_exec_mapper->io_ul_executor(), + *dependencies.workers->non_rt_low_prio_exec); + ngu_gws.push_back(std::move(ngu_gw)); + } } else { - ngu_gw = srs_cu_up::create_no_core_ngu_gateway(); + std::unique_ptr ngu_gw = create_no_core_gtpu_gateway(); + ngu_gws.push_back(std::move(ngu_gw)); + } + + // Pass NG-U gateways to CU-UP + ocu_up_dependencies.cu_dependencies.ngu_gws.resize(ngu_gws.size()); + for (size_t i = 0; i < ngu_gws.size(); i++) { + ocu_up_dependencies.cu_dependencies.ngu_gws[i] = ngu_gws[i].get(); } - ocu_up_dependencies.cu_dependencies.ngu_gw = ngu_gw.get(); auto e2_metric_connectors = std::make_unique(); @@ -110,7 +126,7 @@ o_cu_up_unit srsran::build_o_cu_up(const o_cu_up_unit_config& unit_cfg, const o_ } ocu_unit.unit = - std::make_unique(std::move(ngu_gw), + std::make_unique(std::move(ngu_gws), std::move(e2_metric_connectors), srs_cu_up::create_o_cu_up(config, std::move(ocu_up_dependencies))); diff --git a/apps/units/o_cu_up/o_cu_up_unit_impl.cpp b/apps/units/o_cu_up/o_cu_up_unit_impl.cpp index d7350144a9..62b5188bb0 100644 --- a/apps/units/o_cu_up/o_cu_up_unit_impl.cpp +++ b/apps/units/o_cu_up/o_cu_up_unit_impl.cpp @@ -24,12 +24,12 @@ using namespace srsran; -o_cu_up_unit_impl::o_cu_up_unit_impl(std::unique_ptr gateway_, +o_cu_up_unit_impl::o_cu_up_unit_impl(std::vector> gateways_, std::unique_ptr e2_metric_connector_, std::unique_ptr cu_up_) : - gateway(std::move(gateway_)), e2_metric_connector(std::move(e2_metric_connector_)), cu_up(std::move(cu_up_)) + gateways(std::move(gateways_)), e2_metric_connector(std::move(e2_metric_connector_)), cu_up(std::move(cu_up_)) { - srsran_assert(gateway, "Invlid NGU gateway"); + srsran_assert(not gateways.empty(), "Invalid NG-U gateway"); srsran_assert(cu_up, "Invalid CU-UP"); } diff --git a/apps/units/o_cu_up/o_cu_up_unit_impl.h b/apps/units/o_cu_up/o_cu_up_unit_impl.h index 749b0cb498..862d7d604b 100644 --- a/apps/units/o_cu_up/o_cu_up_unit_impl.h +++ b/apps/units/o_cu_up/o_cu_up_unit_impl.h @@ -26,7 +26,7 @@ #include "srsran/cu_up/cu_up_power_controller.h" #include "srsran/cu_up/o_cu_up.h" #include "srsran/e2/e2_cu_metrics_connector.h" -#include "srsran/gtpu/ngu_gateway.h" +#include "srsran/gtpu/gtpu_gateway.h" namespace srsran { @@ -39,7 +39,7 @@ using e2_cu_metrics_connector_manager = class o_cu_up_unit_impl : public srs_cu_up::o_cu_up { public: - o_cu_up_unit_impl(std::unique_ptr gateway_, + o_cu_up_unit_impl(std::vector> gateways_, std::unique_ptr e2_metric_connector_, std::unique_ptr cu_up_); @@ -50,7 +50,7 @@ class o_cu_up_unit_impl : public srs_cu_up::o_cu_up srs_cu_up::cu_up_power_controller& get_power_controller() override; private: - std::unique_ptr gateway; + std::vector> gateways; std::unique_ptr e2_metric_connector; std::unique_ptr cu_up; }; diff --git a/apps/units/o_cu_up/pcap_factory.h b/apps/units/o_cu_up/pcap_factory.h index 13982f98d3..f4ac551800 100644 --- a/apps/units/o_cu_up/pcap_factory.h +++ b/apps/units/o_cu_up/pcap_factory.h @@ -38,6 +38,7 @@ struct o_cu_up_dlt_pcaps { n3.reset(); f1u.reset(); e1ap.reset(); + e2ap.reset(); } }; diff --git a/configs/cell_cfg_max_128_ues.yml b/configs/cell_cfg_max_128_ues.yml index 9cd24c5b24..fdf7dffa6a 100644 --- a/configs/cell_cfg_max_128_ues.yml +++ b/configs/cell_cfg_max_128_ues.yml @@ -12,7 +12,7 @@ cell_cfg: pucch: sr_period_ms: 20 # This can be set either 20 or 40 ms. f0_or_f1_nof_cell_res_sr: 50 - f2_nof_cell_res_csi: 50 + f2_or_f3_or_f4_nof_cell_res_csi: 50 csi: csi_rs_period: 40 # This can be set either 20 or 40 ms. ul_common: diff --git a/configs/cell_cfg_max_256_ues.yml b/configs/cell_cfg_max_256_ues.yml index 5441a8d6f3..f70bfc3215 100644 --- a/configs/cell_cfg_max_256_ues.yml +++ b/configs/cell_cfg_max_256_ues.yml @@ -14,7 +14,7 @@ cell_cfg: nof_ue_res_harq_per_set: 8 nof_cell_harq_pucch_res_sets: 2 # Increase this if UEs are not scheduled PDSCH due to PUCCH resources starvation. f0_or_f1_nof_cell_res_sr: 50 - f2_nof_cell_res_csi: 50 + f2_or_f3_or_f4_nof_cell_res_csi: 50 csi: csi_rs_period: 40 # This can be set either 20 or 40 ms. ul_common: diff --git a/configs/cell_cfg_max_32_ues.yml b/configs/cell_cfg_max_32_ues.yml index 62a808ef86..1989d236ab 100644 --- a/configs/cell_cfg_max_32_ues.yml +++ b/configs/cell_cfg_max_32_ues.yml @@ -10,7 +10,7 @@ cell_cfg: pucch: sr_period_ms: 20 # This can be set either 20 or 40 ms. f0_or_f1_nof_cell_res_sr: 15 - f2_nof_cell_res_csi: 15 + f2_or_f3_or_f4_nof_cell_res_csi: 15 csi: csi_rs_period: 40 # This can be set either 20 or 40 ms. ul_common: diff --git a/configs/cell_cfg_max_512_ues.yml b/configs/cell_cfg_max_512_ues.yml index 71afd39b89..044d22eae9 100644 --- a/configs/cell_cfg_max_512_ues.yml +++ b/configs/cell_cfg_max_512_ues.yml @@ -15,7 +15,7 @@ cell_cfg: nof_ue_res_harq_per_set: 8 nof_cell_harq_pucch_res_sets: 2 # Increase this if UEs are not scheduled PDSCH due to PUCCH resources starvation. f0_or_f1_nof_cell_res_sr: 80 - f2_nof_cell_res_csi: 80 + f2_or_f3_or_f4_nof_cell_res_csi: 80 csi: csi_rs_period: 40 # This can be set to 20 only with TDD 6D-3U. ul_common: diff --git a/configs/cell_cfg_max_64_ues.yml b/configs/cell_cfg_max_64_ues.yml index 8c9e472f15..c8ae93d490 100644 --- a/configs/cell_cfg_max_64_ues.yml +++ b/configs/cell_cfg_max_64_ues.yml @@ -10,7 +10,7 @@ cell_cfg: pucch: sr_period_ms: 20 # This can be set either 20 or 40 ms. f0_or_f1_nof_cell_res_sr: 31 - f2_nof_cell_res_csi: 31 + f2_or_f3_or_f4_nof_cell_res_csi: 31 csi: csi_rs_period: 40 # This can be set either 20 or 40 ms. ul_common: diff --git a/configs/gnb_rf_b210_fdd_srsUE.yml b/configs/gnb_rf_b210_fdd_srsUE.yml index 5096a1c434..2f62626095 100644 --- a/configs/gnb_rf_b210_fdd_srsUE.yml +++ b/configs/gnb_rf_b210_fdd_srsUE.yml @@ -41,6 +41,10 @@ cell_cfg: coreset0_index: 12 prach: prach_config_index: 1 + pdsch: + mcs_table: qam64 + pusch: + mcs_table: qam64 log: filename: /tmp/gnb.log diff --git a/configs/ngu_multiple_sockets.yml b/configs/ngu_multiple_sockets.yml new file mode 100644 index 0000000000..58be0adcc9 --- /dev/null +++ b/configs/ngu_multiple_sockets.yml @@ -0,0 +1,17 @@ + +cu_up: + ngu: + socket: + - + bind_addr: 127.0.3.1 # Address that the NG-U socket will bind to. + udp: + max_rx_msgs: 256 # Maximum packets read from the socket in a single syscall. + pool_threshold: 0.9 # Pool occupancy threshold, after which packets are dropped. + #dscp: 3 # Differentiated Services Code Point. + + - + bind_addr: 127.0.3.2 # Address that the NG-U socket will bind to. + udp: + max_rx_msgs: 256 # maximum packets read from the socket in a single syscall. + pool_threshold: 0.9 # Pool occupancy threshold, after which packets are dropped. + #dscp: 4 # Differentiated Services Code Point. diff --git a/external/fmt/include/fmt/LICENSE.rst b/external/fmt/include/fmt/LICENSE similarity index 95% rename from external/fmt/include/fmt/LICENSE.rst rename to external/fmt/include/fmt/LICENSE index f0ec3db4d2..1cd1ef9269 100644 --- a/external/fmt/include/fmt/LICENSE.rst +++ b/external/fmt/include/fmt/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/external/fmt/include/fmt/args.h b/external/fmt/include/fmt/args.h new file mode 100644 index 0000000000..31a60e8faf --- /dev/null +++ b/external/fmt/include/fmt/args.h @@ -0,0 +1,228 @@ +// Formatting library for C++ - dynamic argument lists +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_ARGS_H_ +#define FMT_ARGS_H_ + +#ifndef FMT_MODULE +# include // std::reference_wrapper +# include // std::unique_ptr +# include +#endif + +#include "format.h" // std_string_view + +FMT_BEGIN_NAMESPACE + +namespace detail { + +template struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; + +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { + return static_cast(v); +} + +// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC +// 2022 (v17.10.0). +// +// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for +// templates it doesn't complain about inability to deduce single translation +// unit for placing vtable. So node is made a fake template. +template struct node { + virtual ~node() = default; + std::unique_ptr> next; +}; + +class dynamic_arg_list { + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template auto push(const Arg& arg) -> const T& { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + * A dynamic list of formatting arguments with storage. + * + * It can be implicitly converted into `fmt::basic_format_args` for passing + * into type-erased formatting functions such as `fmt::vformat`. + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; + + enum { + value = !(detail::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template + using stored_type = conditional_t< + std::is_convertible>::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + std::vector> named_info_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + detail::dynamic_arg_list dynamic_args_; + + friend class basic_format_args; + + auto get_types() const -> unsigned long long { + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast(detail::has_named_args_bit)); + } + + auto data() const -> const basic_format_arg* { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(detail::make_arg(arg)); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + constexpr dynamic_format_arg_store() = default; + + /** + * Adds an argument into the dynamic store for later passing to a formatting + * function. + * + * Note that custom types and string types (but not string views) are copied + * into the store dynamically allocating memory if necessary. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * store.push_back(42); + * store.push_back("abc"); + * store.push_back(1.5f); + * std::string result = fmt::vformat("{} and {} and {}", store); + */ + template void push_back(const T& arg) { + if (detail::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + * Adds a reference to the argument into the dynamic store for later passing + * to a formatting function. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * char band[] = "Rolling Stones"; + * store.push_back(std::cref(band)); + * band[9] = 'c'; // Changing str affects the output. + * std::string result = fmt::vformat("{}", store); + * // result == "Rolling Scones" + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } + + /** + * Adds named argument into the dynamic store for later passing to a + * formatting function. `std::reference_wrapper` is supported to avoid + * copying of the argument. The name is always copied into the store. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /// Erase all elements from the store. + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = detail::dynamic_arg_list(); + } + + /// Reserves space to store at least `new_cap` arguments including + /// `new_cap_named` named arguments. + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/external/fmt/include/fmt/base.h b/external/fmt/include/fmt/base.h new file mode 100644 index 0000000000..6276494253 --- /dev/null +++ b/external/fmt/include/fmt/base.h @@ -0,0 +1,3077 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) +# define FMT_MODULE +#endif + +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // strlen + +// is also included transitively from . +# include // std::byte +# include // std::enable_if +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 110002 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L +// GCC only allows throw in constexpr since version 6: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#if !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif + +#ifdef FMT_USE_CONCEPTS +// Use the provided definition. +#elif defined(__cpp_concepts) +# define FMT_USE_CONCEPTS 1 +#else +# define FMT_USE_CONCEPTS 0 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#else +# define FMT_EXCEPTIONS 1 +#endif +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifndef FMT_NODISCARD +# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +# else +# define FMT_NODISCARD +# endif +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +#ifdef FMT_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_INLINE to prevent code bloat in debug mode. +#ifdef NDEBUG +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +#ifndef FMT_GCC_PRAGMA +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +# else +# define FMT_GCC_PRAGMA(arg) +# endif +#endif + +// GCC < 5 requires this-> in decltype. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +# define FMT_DECLTYPE_THIS this-> +#else +# define FMT_DECLTYPE_THIS +#endif + +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +# define FMT_UNCHECKED_ITERATOR(It) \ + using _Unchecked_type = It // Mark iterator as checked. +#else +# define FMT_MSC_WARNING(...) +# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v11 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +// Check if rtti is available. +#ifndef FMT_USE_RTTI +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +# define FMT_USE_RTTI 1 +# else +# define FMT_USE_RTTI 0 +# endif +#endif + +#define FMT_FWD(...) static_cast(__VA_ARGS__) + +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) +FMT_GCC_PRAGMA("GCC optimize(\"Og\")") +#endif + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template struct type_identity { + using type = T; +}; +template using type_identity_t = typename type_identity::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +// This is defined in base.h instead of format.h to avoid injecting in std. +// It is a template to avoid undesirable implicit conversions to std::byte. +#ifdef __cpp_lib_byte +template ::value)> +inline auto format_as(T b) -> unsigned char { + return static_cast(b); +} +#endif + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between libstdc++ consteval-based +// std::is_constant_evaluated() implementation and clang-14: +// https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template constexpr auto const_check(T value) -> T { return value; } + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Do nothing. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +template inline auto convert_for_visit(T value) -> T { + return value; +} +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +template auto convert_for_visit(T) -> monostate { return {}; } +#endif + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::is_convertible().data()), + const typename T::value_type*> {}; + +// Returns true iff the literal encoding is UTF-8. +constexpr auto is_utf8_enabled() -> bool { + // Avoid an MSVC sign extension bug: https://github.com/fmtlib/fmt/pull/2297. + using uchar = unsigned char; + return sizeof("\u00A7") == 3 && uchar("\u00A7"[0]) == 0xC2 && + uchar("\u00A7"[1]) == 0xA7; +} +constexpr auto use_utf8() -> bool { + return !FMT_MSC_VERSION || is_utf8_enabled(); +} + +static_assert(!FMT_UNICODE || use_utf8(), + "Unicode support requires compiling with /utf-8"); + +template FMT_CONSTEXPR auto length(const Char* s) -> size_t { + size_t len = 0; + while (*s++) ++len; + return len; +} + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) + -> int { + if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +namespace adl { +using namespace std; + +template +auto invoke_back_inserter() + -> decltype(back_inserter(std::declval())); +} // namespace adl + +template +struct is_back_insert_iterator : std::false_type {}; + +template +struct is_back_insert_iterator< + It, bool_constant()), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline auto get_container(OutputIt it) -> typename OutputIt::container_type& { + struct accessor : OutputIt { + accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +FMT_EXPORT +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string reference object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(std::nullptr_t) = delete; + + /// Constructs a string reference object from a C string. + FMT_CONSTEXPR20 + basic_string_view(const Char* s) + : data_(s), + size_(detail::const_check(std::is_same::value && + !detail::is_constant_evaluated(false)) + ? strlen(reinterpret_cast(s)) + : detail::length(s)) {} + + /// Constructs a string reference from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = detail::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +FMT_EXPORT +using string_view = basic_string_view; + +/// Specifies if `T` is a character type. Can be specialized by users. +FMT_EXPORT +template struct is_char : std::false_type {}; +template <> struct is_char : std::true_type {}; + +namespace detail { + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +constexpr auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const T& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; +#if FMT_CPLUSPLUS < 201703L +template +constexpr Char string_literal::value[sizeof...(C)]; +#endif + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; +} // namespace detail + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +FMT_DEPRECATED FMT_NORETURN inline void throw_format_error( + const char* message) { + report_error(message); +} + +/// String's character (code unit) type. +template ()))> +using char_t = typename V::value_type; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + * You can use the `format_parse_context` type alias for `char` instead. + */ +FMT_EXPORT +template class basic_format_parse_context { + private: + basic_string_view format_str_; + int next_arg_id_; + + FMT_CONSTEXPR void do_check_arg_id(int id); + + public: + using char_type = Char; + using iterator = const Char*; + + explicit constexpr basic_format_parse_context( + basic_string_view format_str, int next_arg_id = 0) + : format_str_(format_str), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { + return format_str_.begin(); + } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return format_str_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +FMT_EXPORT +using format_parse_context = basic_format_parse_context; + +namespace detail { +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public basic_format_parse_context { + private: + int num_args_; + const type* types_; + using base = basic_format_parse_context; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view format_str, int num_args, const type* types, + int next_arg_id = 0) + : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + detail::ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR20 buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = count <= capacity_ ? count : capacity_; + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template void append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + // A loop is faster than memcpy on small sizes. + T* out = ptr_ + size_; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + explicit buffer_traits(size_t) {} + auto count() const -> size_t { return 0; } + auto limit(size_t size) -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + auto count() const -> size_t { return count_; } + auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return size < n ? size : n; + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public buffer { + private: + using container_type = typename OutputIt::container_type; + using value_type = typename container_type::value_type; + container_type& container_; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container_.resize(capacity); + self.set(&self.container_[0], capacity); + } + + public: + explicit iterator_buffer(container_type& c) + : buffer(grow, c.size()), container_(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : iterator_buffer(get_container(out)) {} + + auto out() -> OutputIt { return back_inserter(container_); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + auto count() -> size_t { return count_ + this->size(); } +}; +} // namespace detail + +template +FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + if (id >= static_cast(this)->num_args()) + report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( + int arg_id) { + if (detail::is_constant_evaluated() && + (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { + using context = detail::compile_parse_context; + static_cast(this)->check_dynamic_spec(arg_id); + } +} + +FMT_EXPORT template class basic_format_arg; +FMT_EXPORT template class basic_format_args; +FMT_EXPORT template class dynamic_format_arg_store; + +// A formatter for objects of type T. +FMT_EXPORT +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +// Specifies if T has an enabled formatter specialization. A type can be +// formattable even if it doesn't have a formatter e.g. via a conversion. +template +using has_formatter = + std::is_constructible>; + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + private: + detail::buffer* buffer_; + + friend auto get_container(basic_appender app) -> detail::buffer& { + return *app.buffer_; + } + + public: + using iterator_category = int; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; + using container_type = detail::buffer; + FMT_UNCHECKED_ITERATOR(basic_appender); + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : buffer_(&buf) {} + + auto operator=(T c) -> basic_appender& { + buffer_->push_back(c); + return *this; + } + auto operator*() -> basic_appender& { return *this; } + auto operator++() -> basic_appender& { return *this; } + auto operator++(int) -> basic_appender { return *this; } +}; + +using appender = basic_appender; + +namespace detail { +template +struct is_back_insert_iterator> : std::true_type {}; + +template +struct locking : std::true_type {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +// An optimized version of std::copy with the output value type (T). +template ::value)> +auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type().format( + std::declval(), std::declval()), + true) { + return true; +} +template +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl(static_cast(nullptr)); +} + +template +struct is_buffer_appender : std::false_type {}; +template +struct is_buffer_appender< + It, bool_constant< + is_back_insert_iterator::value && + std::is_base_of, + typename It::container_type>::value>> + : std::true_type {}; + +// Maps an output iterator to a buffer. +template ::value)> +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template ::value)> +auto get_buffer(OutputIt out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +struct view {}; + +template struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +template struct named_arg_info { + const Char* name; + int id; +}; + +template struct is_named_arg : std::false_type {}; +template struct is_statically_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template constexpr auto count() -> size_t { return B ? 1 : 0; } +template constexpr auto count() -> size_t { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> size_t { + return count::value...>(); +} + +template +constexpr auto count_statically_named_args() -> size_t { + return count::value...>(); +} + +struct unformattable {}; +struct unformattable_char : unformattable {}; +struct unformattable_pointer : unformattable {}; + +template struct string_value { + const Char* data; + size_t size; +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +template struct custom_value { + using parse_context = typename Context::parse_context_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_ALWAYS_INLINE value() : no_value() {} + constexpr FMT_ALWAYS_INLINE value(int val) : int_value(val) {} + constexpr FMT_ALWAYS_INLINE value(unsigned val) : uint_value(val) {} + constexpr FMT_ALWAYS_INLINE value(long long val) : long_long_value(val) {} + constexpr FMT_ALWAYS_INLINE value(unsigned long long val) + : ulong_long_value(val) {} + FMT_ALWAYS_INLINE value(int128_opt val) : int128_value(val) {} + FMT_ALWAYS_INLINE value(uint128_opt val) : uint128_value(val) {} + constexpr FMT_ALWAYS_INLINE value(float val) : float_value(val) {} + constexpr FMT_ALWAYS_INLINE value(double val) : double_value(val) {} + FMT_ALWAYS_INLINE value(long double val) : long_double_value(val) {} + constexpr FMT_ALWAYS_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_ALWAYS_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_ALWAYS_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_ALWAYS_INLINE value(basic_string_view val) { + string.data = val.data(); + string.size = val.size(); + } + FMT_ALWAYS_INLINE value(const void* val) : pointer(val) {} + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + template FMT_CONSTEXPR20 FMT_ALWAYS_INLINE value(T& val) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same::value) + custom.value = const_cast(&val); +#endif + if (!is_constant_evaluated()) + custom.value = const_cast(&reinterpret_cast(val)); + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + custom.format = format_custom_arg< + value_type, typename Context::template formatter_type>; + } + value(unformattable); + value(unformattable_char); + value(unformattable_pointer); + + private: + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg(void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template struct format_as_result { + template ::value || std::is_class::value)> + static auto map(U*) -> remove_cvref_t()))>; + static auto map(...) -> void; + + using type = decltype(map(static_cast(nullptr))); +}; +template using format_as_t = typename format_as_result::type; + +template +struct has_format_as + : bool_constant, void>::value> {}; + +#define FMT_MAP_API FMT_CONSTEXPR FMT_ALWAYS_INLINE + +// Maps formatting arguments to core types. +// arg_mapper reports errors by returning unformattable instead of using +// static_assert because it's used in the is_formattable trait. +template struct arg_mapper { + using char_type = typename Context::char_type; + + FMT_MAP_API auto map(signed char val) -> int { return val; } + FMT_MAP_API auto map(unsigned char val) -> unsigned { return val; } + FMT_MAP_API auto map(short val) -> int { return val; } + FMT_MAP_API auto map(unsigned short val) -> unsigned { return val; } + FMT_MAP_API auto map(int val) -> int { return val; } + FMT_MAP_API auto map(unsigned val) -> unsigned { return val; } + FMT_MAP_API auto map(long val) -> long_type { return val; } + FMT_MAP_API auto map(unsigned long val) -> ulong_type { return val; } + FMT_MAP_API auto map(long long val) -> long long { return val; } + FMT_MAP_API auto map(unsigned long long val) -> unsigned long long { + return val; + } + FMT_MAP_API auto map(int128_opt val) -> int128_opt { return val; } + FMT_MAP_API auto map(uint128_opt val) -> uint128_opt { return val; } + FMT_MAP_API auto map(bool val) -> bool { return val; } + + template ::value || + std::is_same::value)> + FMT_MAP_API auto map(T val) -> char_type { + return val; + } + template ::value || +#ifdef __cpp_char8_t + std::is_same::value || +#endif + std::is_same::value || + std::is_same::value) && + !std::is_same::value, + int> = 0> + FMT_MAP_API auto map(T) -> unformattable_char { + return {}; + } + + FMT_MAP_API auto map(float val) -> float { return val; } + FMT_MAP_API auto map(double val) -> double { return val; } + FMT_MAP_API auto map(long double val) -> long double { return val; } + + FMT_MAP_API auto map(char_type* val) -> const char_type* { return val; } + FMT_MAP_API auto map(const char_type* val) -> const char_type* { return val; } + template , + FMT_ENABLE_IF(std::is_same::value && + !std::is_pointer::value)> + FMT_MAP_API auto map(const T& val) -> basic_string_view { + return to_string_view(val); + } + template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_pointer::value)> + FMT_MAP_API auto map(const T&) -> unformattable_char { + return {}; + } + + FMT_MAP_API auto map(void* val) -> const void* { return val; } + FMT_MAP_API auto map(const void* val) -> const void* { return val; } + FMT_MAP_API auto map(volatile void* val) -> const void* { + return const_cast(val); + } + FMT_MAP_API auto map(const volatile void* val) -> const void* { + return const_cast(val); + } + FMT_MAP_API auto map(std::nullptr_t val) -> const void* { return val; } + + // Use SFINAE instead of a const T* parameter to avoid a conflict with the + // array overload. + template < + typename T, + FMT_ENABLE_IF( + std::is_pointer::value || std::is_member_pointer::value || + std::is_function::type>::value || + (std::is_array::value && + !std::is_convertible::value))> + FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { + return {}; + } + + template ::value)> + FMT_MAP_API auto map(const T (&values)[N]) -> const T (&)[N] { + return values; + } + + // Only map owning types because mapping views can be unsafe. + template , + FMT_ENABLE_IF(std::is_arithmetic::value)> + FMT_MAP_API auto map(const T& val) -> decltype(FMT_DECLTYPE_THIS map(U())) { + return map(format_as(val)); + } + + template > + struct formattable : bool_constant() || + (has_formatter::value && + !std::is_const::value)> {}; + + template ::value)> + FMT_MAP_API auto do_map(T& val) -> T& { + return val; + } + template ::value)> + FMT_MAP_API auto do_map(T&) -> unformattable { + return {}; + } + + // is_fundamental is used to allow formatters for extended FP types. + template , + FMT_ENABLE_IF( + (std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_fundamental::value) && + !has_to_string_view::value && !is_char::value && + !is_named_arg::value && !std::is_integral::value && + !std::is_arithmetic>::value)> + FMT_MAP_API auto map(T& val) -> decltype(FMT_DECLTYPE_THIS do_map(val)) { + return do_map(val); + } + + template ::value)> + FMT_MAP_API auto map(const T& named_arg) + -> decltype(FMT_DECLTYPE_THIS map(named_arg.value)) { + return map(named_arg.value); + } + + auto map(...) -> unformattable { return {}; } +}; + +// A type constant after applying arg_mapper. +template +using mapped_type_constant = + type_constant().map(std::declval())), + typename Context::char_type>; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, void_t()++ = std::declval())>> + : std::true_type {}; + +// A type-erased reference to an std::locale to avoid a heavy include. +class locale_ref { + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + template explicit locale_ref(const Locale& loc); + + explicit operator bool() const noexcept { return locale_ != nullptr; } + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(mapped_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr unsigned long long make_descriptor() { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +// This type is intentionally undefined, only used for errors. +template +#if FMT_CLANG_VERSION && FMT_CLANG_VERSION <= 1500 +// https://github.com/fmtlib/fmt/issues/3796 +struct type_is_unformattable_for { +}; +#else +struct type_is_unformattable_for; +#endif + +template +FMT_CONSTEXPR auto make_arg(T& val) -> value { + using arg_type = remove_cvref_t().map(val))>; + + // Use enum instead of constexpr because the latter may generate code. + enum { + formattable_char = !std::is_same::value + }; + static_assert(formattable_char, "Mixing character types is disallowed."); + + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + enum { + formattable_pointer = !std::is_same::value + }; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + enum { formattable = !std::is_same::value }; +#if defined(__cpp_if_constexpr) + if constexpr (!formattable) + type_is_unformattable_for _; +#endif + static_assert( + formattable, + "Cannot format an argument. To make type T formattable provide a " + "formatter specialization: https://fmt.dev/latest/api.html#udt"); + return {arg_mapper().map(val)}; +} + +template +FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg { + auto arg = basic_format_arg(); + arg.type_ = mapped_type_constant::value; + arg.value_ = make_arg(val); + return arg; +} + +template +FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg { + return make_arg(val); +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +// An array of references to arguments. It can be implicitly converted to +// `fmt::basic_format_args` for passing into type-erased formatting functions +// such as `fmt::vformat`. +template +struct format_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + static constexpr size_t ARGS_ARR_SIZE = 1 + (NUM_ARGS != 0 ? NUM_ARGS : +1); + + arg_t args[ARGS_ARR_SIZE]; + named_arg_info named_args[NUM_NAMED_ARGS]; + + template + FMT_MAP_API format_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, + make_arg(values)...} { + using dummy = int[]; + int arg_index = 0, named_arg_index = 0; + (void)dummy{ + 0, + (init_named_arg(named_args, arg_index, named_arg_index, values), 0)...}; + } + + format_arg_store(format_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < ARGS_ARR_SIZE; ++i) args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + format_arg_store(const format_arg_store& rhs) = delete; + format_arg_store& operator=(const format_arg_store& rhs) = delete; + format_arg_store& operator=(format_arg_store&& rhs) = delete; +}; + +// A specialization of format_arg_store without named arguments. +// It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + arg_t args[NUM_ARGS != 0 ? NUM_ARGS : +1]; +}; + +} // namespace detail +FMT_BEGIN_EXPORT + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + template + friend FMT_CONSTEXPR auto detail::make_arg(T& value) + -> basic_format_arg; + + friend class basic_format_args; + friend class dynamic_format_arg_store; + + using char_type = typename Context::char_type; + + template + friend struct detail::format_arg_store; + + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + + public: + class handle { + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(typename Context::parse_context_type& parse_ctx, + Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + + private: + detail::custom_value custom_; + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + + auto type() const -> detail::type { return type_; } + + auto is_integral() const -> bool { return detail::is_integral_type(type_); } + auto is_arithmetic() const -> bool { + return detail::is_arithmetic_type(type_); + } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { + switch (type_) { + case detail::type::none_type: + break; + case detail::type::int_type: + return vis(value_.int_value); + case detail::type::uint_type: + return vis(value_.uint_value); + case detail::type::long_long_type: + return vis(value_.long_long_value); + case detail::type::ulong_long_type: + return vis(value_.ulong_long_value); + case detail::type::int128_type: + return vis(detail::convert_for_visit(value_.int128_value)); + case detail::type::uint128_type: + return vis(detail::convert_for_visit(value_.uint128_value)); + case detail::type::bool_type: + return vis(value_.bool_value); + case detail::type::char_type: + return vis(value_.char_value); + case detail::type::float_type: + return vis(value_.float_value); + case detail::type::double_type: + return vis(value_.double_value); + case detail::type::long_double_type: + return vis(value_.long_double_value); + case detail::type::cstring_type: + return vis(value_.string.data); + case detail::type::string_type: + using sv = basic_string_view; + return vis(sv(value_.string.data, value_.string.size)); + case detail::type::pointer_type: + return vis(value_.pointer); + case detail::type::custom_type: + return vis(typename basic_format_arg::handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + typename Context::parse_context_type& parse_ctx, + Context& ctx) -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +template +FMT_DEPRECATED FMT_CONSTEXPR auto visit_format_arg( + Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { + return arg.visit(static_cast(vis)); +} + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + public: + using size_type = int; + using format_arg = basic_format_arg; + + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + public: + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const detail::format_arg_store& + store) + : desc_(DESC), values_(store.args + (NUM_NAMED_ARGS != 0 ? 1 : 0)) {} + + template detail::max_packed_args)> + constexpr basic_format_args( + const detail::format_arg_store& + store) + : desc_(DESC), args_(store.args + (NUM_NAMED_ARGS != 0 ? 1 : 0)) {} + + /// Constructs a `basic_format_args` object from `dynamic_format_arg_store`. + constexpr basic_format_args(const dynamic_format_arg_store& store) + : desc_(store.get_types()), args_(store.data()) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + basic_format_args args_; + detail::locale_ref loc_; + + public: + /// The character type for the output. + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + /// Constructs a `basic_format_context` object. References to the arguments + /// are stored in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, basic_format_args ctx_args, + detail::locale_ref loc = {}) + : out_(out), args_(ctx_args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + auto arg(string_view name) -> format_arg { return args_.get(name); } + FMT_CONSTEXPR auto arg_id(string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { return args_; } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + // Advances the begin iterator to `it`. + void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } +}; + +template class generic_context; + +// Longer aliases for C++20 compatibility. +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = basic_format_context, Char>; + +template +using is_formattable = bool_constant>() + .map(std::declval()))>::value>; + +#if FMT_USE_CONCEPTS +template +concept formattable = is_formattable, Char>::value; +#endif + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `format_context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor(), + FMT_ENABLE_IF(NUM_NAMED_ARGS == 0)> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + return {{detail::make_arg( + args)...}}; +} + +#ifndef FMT_DOC +template (), + unsigned long long DESC = + detail::make_descriptor() | + static_cast(detail::has_named_args_bit), + FMT_ENABLE_IF(NUM_NAMED_ARGS != 0)> +constexpr auto make_format_args(T&... args) + -> detail::format_arg_store { + return {args...}; +} +#endif + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function or + * `dynamic_format_arg_store::push_back`. + * + * **Example**: + * + * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + static_assert(!detail::is_named_arg(), "nested named arguments"); + return {name, arg}; +} +FMT_END_EXPORT + +/// An alias for `basic_format_args`. +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +FMT_EXPORT using format_args = basic_format_args; + +// We cannot use enum classes as bit fields because of a gcc bug, so we put them +// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). +// Additionally, if an underlying type is specified, older gcc incorrectly warns +// that the type is too small. Both bugs are fixed in gcc 9.3. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 +# define FMT_ENUM_UNDERLYING_TYPE(type) +#else +# define FMT_ENUM_UNDERLYING_TYPE(type) : type +#endif +namespace align { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, + numeric}; +} +using align_t = align::type; +namespace sign { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; +} +using sign_t = sign::type; + +namespace detail { + +template +using unsigned_char = typename conditional_t::value, + std::make_unsigned, + type_identity>::type; + +// Character (code unit) type is erased to prevent template bloat. +struct fill_t { + private: + enum { max_size = 4 }; + char data_[max_size] = {' '}; + unsigned char size_ = 1; + + public: + template + FMT_CONSTEXPR void operator=(basic_string_view s) { + auto size = s.size(); + size_ = static_cast(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + data_[0] = static_cast(uchar); + data_[1] = static_cast(uchar >> 8); + return; + } + FMT_ASSERT(size <= max_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = static_cast(s[i]); + } + + FMT_CONSTEXPR void operator=(char c) { + data_[0] = c; + size_ = 1; + } + + constexpr auto size() const -> size_t { return size_; } + + template constexpr auto get() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(data_[0]) | + (static_cast(data_[1]) << 8)); + } + + template ::value)> + constexpr auto data() const -> const Char* { + return data_; + } + template ::value)> + constexpr auto data() const -> const Char* { + return nullptr; + } +}; +} // namespace detail + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +// Format specifiers for built-in and string types. +struct format_specs { + int width; + int precision; + presentation_type type; + align_t align : 4; + sign_t sign : 3; + bool upper : 1; // An uppercase version e.g. 'X' for 'x'. + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t fill; + + constexpr format_specs() + : width(0), + precision(-1), + type(presentation_type::none), + align(align::none), + sign(sign::none), + upper(false), + alt(false), + localized(false) {} +}; + +namespace detail { + +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int idx = 0) : index(idx) {} + FMT_CONSTEXPR value(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; +} + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = + static_cast(memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { + switch (c) { + case '<': + return align::left; + case '>': + return align::right; + case '^': + return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (!is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler.on_auto(); + return begin; +} + +template struct dynamic_spec_id_handler { + basic_format_parse_context& ctx; + arg_ref& ref; + + FMT_CONSTEXPR void on_auto() { + int id = ctx.next_arg_id(); + ref = arg_ref(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_index(int id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = arg_ref(id); + ctx.check_arg_id(id); + } +}; + +// Parses [integer | "{" [arg_id] "}"]. +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) + -> const Char* { + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val != -1) + value = val; + else + report_error("number is too big"); + } else if (*begin == '{') { + ++begin; + auto handler = dynamic_spec_id_handler{ctx, ref}; + if (begin != end) begin = parse_arg_id(begin, end, handler); + if (begin != end && *begin == '}') return ++begin; + report_error("invalid format string"); + } + return begin; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + int& value, arg_ref& ref, + basic_format_parse_context& ctx) + -> const Char* { + ++begin; + if (begin == end || *begin == '}') { + report_error("invalid precision"); + return begin; + } + return parse_dynamic_spec(begin, end, value, ref, ctx); +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + basic_format_parse_context& ctx, + type arg_type) -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + dynamic_format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) { + if (arg_type == type::none_type) return begin; + report_error("invalid format specifier"); + } + specs.type = pres_type; + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.align = parse_align(c); + ++begin; + break; + case '+': + case '-': + case ' ': + if (arg_type == type::none_type) return begin; + enter_state(state::sign, in(arg_type, sint_set | float_set)); + switch (c) { + case '+': + specs.sign = sign::plus; + break; + case '-': + specs.sign = sign::minus; + break; + case ' ': + specs.sign = sign::space; + break; + } + ++begin; + break; + case '#': + if (arg_type == type::none_type) return begin; + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.alt = true; + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) { + if (arg_type == type::none_type) return begin; + report_error("format specifier requires numeric argument"); + } + if (specs.align == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.align = align::numeric; + specs.fill = '0'; + } + ++begin; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '{': + enter_state(state::width); + begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); + break; + case '.': + if (arg_type == type::none_type) return begin; + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs.precision, specs.precision_ref, + ctx); + break; + case 'L': + if (arg_type == type::none_type) return begin; + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.localized = true; + ++begin; + break; + case 'd': + return parse_presentation_type(pres::dec, integral_set); + case 'X': + specs.upper = true; + FMT_FALLTHROUGH; + case 'x': + return parse_presentation_type(pres::hex, integral_set); + case 'o': + return parse_presentation_type(pres::oct, integral_set); + case 'B': + specs.upper = true; + FMT_FALLTHROUGH; + case 'b': + return parse_presentation_type(pres::bin, integral_set); + case 'E': + specs.upper = true; + FMT_FALLTHROUGH; + case 'e': + return parse_presentation_type(pres::exp, float_set); + case 'F': + specs.upper = true; + FMT_FALLTHROUGH; + case 'f': + return parse_presentation_type(pres::fixed, float_set); + case 'G': + specs.upper = true; + FMT_FALLTHROUGH; + case 'g': + return parse_presentation_type(pres::general, float_set); + case 'A': + specs.upper = true; + FMT_FALLTHROUGH; + case 'a': + return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': + return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto align = parse_align(to_ascii(*fill_end)); + enter_state(state::align, align != align::none); + specs.fill = + basic_string_view(begin, to_unsigned(fill_end - begin)); + specs.align = align; + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + }; + + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view format_str, + Handler&& handler) { + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { + if (from == to) return; + for (;;) { + const Char* p = nullptr; + if (!find(from, to, Char('}'), p)) + return handler_.on_text(from, to); + ++p; + if (p == to || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(from, p); + from = p + 1; + } + } + Handler& handler_; + } write = {handler}; + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char* p = begin; + if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template ::value> struct strip_named_arg { + using type = T; +}; +template struct strip_named_arg { + using type = remove_cvref_t; +}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) + -> decltype(ctx.begin()) { + using char_type = typename ParseContext::char_type; + using context = buffered_context; + using mapped_type = conditional_t< + mapped_type_constant::value != type::custom_type, + decltype(arg_mapper().map(std::declval())), + typename strip_named_arg::type>; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_default_constructible< + formatter>::value) { + return formatter().parse(ctx); + } else { + type_is_unformattable_for _; + return ctx.begin(); + } +#else + return formatter().parse(ctx); +#endif +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + if (specs.type != presentation_type::none && + specs.type != presentation_type::chr && + specs.type != presentation_type::debug) { + return false; + } + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + report_error("invalid format specifier for char"); + return true; +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_statically_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +#endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +#endif + (void)name; + return -1; +} + +template class format_string_checker { + private: + using parse_context_type = compile_parse_context; + static constexpr int num_args = sizeof...(Args); + + // Format specifier parsing function. + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. + using parse_func = const Char* (*)(parse_context_type&); + + type types_[num_args > 0 ? static_cast(num_args) : 1]; + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; + + public: + explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) + : types_{mapped_type_constant>::value...}, + context_(fmt, num_args, types_), + parse_funcs_{&parse_format_specs...} {} + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + return context_.check_arg_id(id), id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + auto index = get_arg_index_by_name(id); + if (index < 0) on_error("named argument is not found"); + return index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); + return 0; +#endif + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) + -> const Char* { + context_.advance_to(begin); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +// A base class for compile-time strings. +struct compile_string {}; + +template +using is_compile_string = std::is_base_of; + +// Reports a compile-time error if S is not a valid format string. +template ::value)> +FMT_ALWAYS_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template ::value)> +void check_format_string(S format_str) { + using char_t = typename S::char_type; + FMT_CONSTEXPR auto s = basic_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true); + ignore_unused(error); +} + +// Report truncation to prevent silent data loss. +inline void report_truncation(bool truncated) { + if (truncated) report_error("output is truncated"); +} + +// Use vformat_args and avoid type_identity to keep symbols short and workaround +// a GCC <= 4.8 bug. +template struct vformat_args { + using type = basic_format_args>; +}; +template <> struct vformat_args { + using type = format_args; +}; + +template +void vformat_to(buffer& buf, basic_string_view fmt, + typename vformat_args::type args, locale_ref loc = {}); + +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool = false); +#ifndef _WIN32 +inline void vprint_mojibake(FILE*, string_view, format_args, bool) {} +#endif + +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.type = set ? presentation_type::debug : presentation_type::none; + } + + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; +} // namespace detail + +FMT_BEGIN_EXPORT + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/// A compile-time format string. +template class basic_format_string { + private: + basic_string_view str_; + + public: + template < + typename S, + FMT_ENABLE_IF( + std::is_convertible>::value || + (detail::is_compile_string::value && + std::is_constructible, const S&>::value))> + FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_format_string(const S& s) : str_(s) { + static_assert( + detail::count< + (std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); +#if FMT_USE_CONSTEVAL + if constexpr (detail::count_named_args() == + detail::count_statically_named_args()) { + using checker = + detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s)); + } +#else + detail::check_format_string(s); +#endif + } + basic_format_string(runtime_format_string fmt) : str_(fmt.str) {} + + FMT_ALWAYS_INLINE operator basic_string_view() const { return str_; } + auto get() const -> basic_string_view { return str_; } +}; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template using format_string = string_view; +inline auto runtime(string_view s) -> string_view { return s; } +#else +template +using format_string = basic_format_string...>; +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } +#endif + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(FMT_FWD(out), fmt, fmt::make_format_args(args...)); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); +} + +template +struct format_to_result { + /// Iterator pointing to just after the last successful write in the range. + OutputIt out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator OutputIt&() & { + detail::report_truncation(truncated); + return out; + } + FMT_CONSTEXPR operator const OutputIt&() const& { + detail::report_truncation(truncated); + return out; + } + FMT_CONSTEXPR operator OutputIt&&() && { + detail::report_truncation(truncated); + return static_cast(out); + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = fmt::format_to_n(out, N, fmt, static_cast(args)...); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt, fmt::make_format_args(args...), {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + if (!detail::use_utf8()) return detail::vprint_mojibake(stdout, fmt, vargs); + return detail::is_locking() ? vprint_buffered(stdout, fmt, vargs) + : vprint(fmt, vargs); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + if (!detail::use_utf8()) return detail::vprint_mojibake(f, fmt, vargs); + return detail::is_locking() ? vprint_buffered(f, fmt, vargs) + : vprint(f, fmt, vargs); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::use_utf8() ? vprintln(f, fmt, vargs) + : detail::vprint_mojibake(f, fmt, vargs, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_END_EXPORT +FMT_GCC_PRAGMA("GCC pop_options") +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/external/fmt/include/fmt/chrono.h b/external/fmt/include/fmt/chrono.h index 93cf376ed9..c93123fd33 100644 --- a/external/fmt/include/fmt/chrono.h +++ b/external/fmt/include/fmt/chrono.h @@ -8,16 +8,54 @@ #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ -#include -#include -#include -#include +#ifndef FMT_MODULE +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif #include "format.h" -#include "locale.h" FMT_BEGIN_NAMESPACE +// Check if std::chrono::local_t is available. +#ifndef FMT_USE_LOCAL_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_LOCAL_TIME 0 +# endif +#endif + +// Check if std::chrono::utc_timestamp is available. +#ifndef FMT_USE_UTC_TIME +# ifdef __cpp_lib_chrono +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +# else +# define FMT_USE_UTC_TIME 0 +# endif +#endif + +// Enable tzset. +#ifndef FMT_USE_TZSET +// UWP doesn't provide _tzset. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) +# define FMT_USE_TZSET 1 +# else +# define FMT_USE_TZSET 0 +# endif +#endif + // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -36,7 +74,8 @@ template ::value && std::numeric_limits::is_signed == std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; @@ -44,7 +83,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { static_assert(T::is_integer, "To must be integral"); // A and B are both signed, or both unsigned. - if (F::digits <= T::digits) { + if (detail::const_check(F::digits <= T::digits)) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. @@ -57,62 +96,47 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return static_cast(from); } -/** - * converts From to To, without loss. If the dynamic value of from - * can't be converted to To without loss, ec is set. - */ +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. template ::value && std::numeric_limits::is_signed != std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); - if (F::is_signed && !T::is_signed) { + if (detail::const_check(F::is_signed && !T::is_signed)) { // From may be negative, not allowed! if (fmt::detail::is_negative(from)) { ec = 1; return {}; } - // From is positive. Can it always fit in To? - if (F::digits <= T::digits) { - // yes, From always fits in To. - } else { - // from may not fit in To, we have to do a dynamic check - if (from > static_cast((T::max)())) { - ec = 1; - return {}; - } + if (detail::const_check(F::digits > T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; } } - if (!F::is_signed && T::is_signed) { - // can from be held in To? - if (F::digits < T::digits) { - // yes, From always fits in To. - } else { - // from may not fit in To, we have to do a dynamic check - if (from > static_cast((T::max)())) { - // outside range. - ec = 1; - return {}; - } - } + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; } - - // reaching here means all is ok for lossless conversion. - return static_cast(from); - -} // function + return static_cast(from); // Lossless conversion. +} template ::value)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; return from; } // function @@ -133,7 +157,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // clang-format on template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; using T = std::numeric_limits; static_assert(std::is_floating_point::value, "From must be floating"); @@ -155,20 +179,18 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; static_assert(std::is_floating_point::value, "From must be floating"); return from; } -/** - * safe duration cast between integral durations - */ +/// Safe duration cast between integral durations template ::value), FMT_ENABLE_IF(std::is_integral::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { using From = std::chrono::duration; ec = 0; // the basic idea is that we need to convert from count() in the from type @@ -190,11 +212,9 @@ To safe_duration_cast(std::chrono::duration from, // safe conversion to IntermediateRep IntermediateRep count = lossless_integral_conversion(from.count(), ec); - if (ec) { - return {}; - } + if (ec) return {}; // multiply with Factor::num without overflow or underflow - if (Factor::num != 1) { + if (detail::const_check(Factor::num != 1)) { const auto max1 = detail::max_value() / Factor::num; if (count > max1) { ec = 1; @@ -202,34 +222,25 @@ To safe_duration_cast(std::chrono::duration from, } const auto min1 = (std::numeric_limits::min)() / Factor::num; - if (count < min1) { + if (detail::const_check(!std::is_unsigned::value) && + count < min1) { ec = 1; return {}; } count *= Factor::num; } - // this can't go wrong, right? den>0 is checked earlier. - if (Factor::den != 1) { - count /= Factor::den; - } - // convert to the to type, safely - using ToRep = typename To::rep; - const ToRep tocount = lossless_integral_conversion(count, ec); - if (ec) { - return {}; - } - return To{tocount}; + if (detail::const_check(Factor::den != 1)) count /= Factor::den; + auto tocount = lossless_integral_conversion(count, ec); + return ec ? To() : To(tocount); } -/** - * safe duration_cast between floating point durations - */ +/// Safe duration_cast between floating point durations template ::value), FMT_ENABLE_IF(std::is_floating_point::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { using From = std::chrono::duration; ec = 0; if (std::isnan(from.count())) { @@ -269,7 +280,7 @@ To safe_duration_cast(std::chrono::duration from, } // multiply with Factor::num without overflow or underflow - if (Factor::num != 1) { + if (detail::const_check(Factor::num != 1)) { constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { @@ -286,7 +297,7 @@ To safe_duration_cast(std::chrono::duration from, } // this can't go wrong, right? den>0 is checked earlier. - if (Factor::den != 1) { + if (detail::const_check(Factor::den != 1)) { using common_t = typename std::common_type::type; count /= static_cast(Factor::den); } @@ -308,38 +319,228 @@ To safe_duration_cast(std::chrono::duration from, #define FMT_NOMACRO namespace detail { -inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } -inline null<> localtime_s(...) { return null<>(); } -inline null<> gmtime_r(...) { return null<>(); } -inline null<> gmtime_s(...) { return null<>(); } +template struct null {}; +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public Streambuf { + private: + using char_type = typename Streambuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename Streambuf::int_type; + using traits_type = typename Streambuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr const size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; + +template +void write_codecvt(codecvt_result& out, string_view in_buf, + const std::locale& loc) { +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet>(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet>(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (detail::use_utf8() && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; +#else + using code_unit = char32_t; +#endif + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy(u.c_str(), u.c_str() + u.size(), out); + } + return copy(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) FMT_THROW(format_error("cannot format duration")); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto fmt_duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t( + std::chrono::time_point time_point) + -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return fmt_duration_cast>( + time_point.time_since_epoch()) + .count(); +} } // namespace detail -// Thread-safe replacement for std::localtime -inline std::tm localtime(std::time_t time) { +FMT_BEGIN_EXPORT + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in local time. Unlike `std::localtime`, this function is + * thread-safe on most platforms. + */ +inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; - std::tm tm_{}; + std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } -#if !FMT_MSC_VER - bool fallback(detail::null<>) { +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { using namespace fmt::detail; - std::tm* tm = std::localtime(&time_); // lgtm[cpp/potentially-dangerous-function] + std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } @@ -351,121 +552,121 @@ inline std::tm localtime(std::time_t time) { return lt.tm_; } -// Thread-safe replacement for std::gmtime -inline std::tm gmtime(std::time_t time) { +#if FMT_USE_LOCAL_TIME +template +inline auto localtime(std::chrono::local_time time) -> std::tm { + return localtime( + detail::to_time_t(std::chrono::current_zone()->to_sys(time))); +} +#endif + +/** + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this + * function is thread-safe on most platforms. + */ +inline auto gmtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} - bool run() { + auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + auto fallback(int res) -> bool { return res == 0; } -#if !FMT_MSC_VER - bool fallback(detail::null<>) { - std::tm* tm = std::gmtime(&time_); // lgtm[cpp/potentially-dangerous-function] +#if !FMT_MSC_VERSION + auto fallback(detail::null<>) -> bool { + std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; - dispatcher gt(time); + auto gt = dispatcher(time); // Too big time values may be unsupported. if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); return gt.tm_; } -namespace detail { -inline size_t strftime(char* str, size_t count, const char* format, - const std::tm* time) { - return std::strftime(str, count, format, time); -} - -inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, - const std::tm* time) { - return std::wcsftime(str, count, format, time); +template +inline auto gmtime( + std::chrono::time_point time_point) + -> std::tm { + return gmtime(detail::to_time_t(time_point)); } -} // namespace detail -template struct formatter { - template - auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto it = ctx.begin(); - if (it != ctx.end() && *it == ':') ++it; - auto end = it; - while (end != ctx.end() && *end != '}') ++end; - tm_format.reserve(detail::to_unsigned(end - it + 1)); - tm_format.append(it, end); - tm_format.push_back('\0'); - return end; - } +namespace detail { - template - auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { - basic_memory_buffer buf; - size_t start = buf.size(); - for (;;) { - size_t size = buf.capacity() - start; - size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); - if (count != 0) { - buf.resize(start + count); - break; - } - if (size >= tm_format.size() * 256) { - // If the buffer is 256 times larger than the format string, assume - // that `strftime` gives an empty result. There doesn't seem to be a - // better way to distinguish the two cases: - // https://github.com/fmtlib/fmt/issues/367 - break; - } - const size_t MIN_GROWTH = 10; - buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); - } - return std::copy(buf.begin(), buf.end(), ctx.out()); +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr const size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); } +} - basic_memory_buffer tm_format; -}; - -namespace detail { -template FMT_CONSTEXPR const char* get_units() { +template +FMT_CONSTEXPR inline auto get_units() -> const char* { + if (std::is_same::value) return "as"; + if (std::is_same::value) return "fs"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) return "µs"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "min"; + if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; return nullptr; } -template <> FMT_CONSTEXPR const char* get_units() { return "as"; } -template <> FMT_CONSTEXPR const char* get_units() { return "fs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ns"; } -template <> FMT_CONSTEXPR const char* get_units() { return "µs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "cs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ds"; } -template <> FMT_CONSTEXPR const char* get_units>() { return "s"; } -template <> FMT_CONSTEXPR const char* get_units() { return "das"; } -template <> FMT_CONSTEXPR const char* get_units() { return "hs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "ks"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ms"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Gs"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ts"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Ps"; } -template <> FMT_CONSTEXPR const char* get_units() { return "Es"; } -template <> FMT_CONSTEXPR const char* get_units>() { - return "m"; -} -template <> FMT_CONSTEXPR const char* get_units>() { - return "h"; -} enum class numeric_system { standard, @@ -473,13 +674,37 @@ enum class numeric_system { alternative }; +// Glibc extensions for formatting numeric values. +enum class pad_type { + // Pad a numeric result string with zeros (the default). + zero, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + // Parses a put_time-like format string and invokes handler actions. template -FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, - const Char* end, - Handler&& handler) { +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; while (ptr != end) { + pad_type pad = pad_type::zero; auto c = *ptr; if (c == '}') break; if (c != '%') { @@ -489,6 +714,18 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -504,6 +741,22 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_text(tab, tab + 1); break; } + // Year: + case 'Y': + handler.on_year(numeric_system::standard); + break; + case 'y': + handler.on_short_year(numeric_system::standard); + break; + case 'C': + handler.on_century(numeric_system::standard); + break; + case 'G': + handler.on_iso_week_based_year(); + break; + case 'g': + handler.on_iso_week_based_short_year(); + break; // Day of the week: case 'a': handler.on_abbr_weekday(); @@ -519,23 +772,46 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Month: case 'b': + case 'h': handler.on_abbr_month(); break; case 'B': handler.on_full_month(); break; + case 'm': + handler.on_dec_month(numeric_system::standard); + break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::standard, pad); + break; + case 'j': + handler.on_day_of_year(); + break; + case 'd': + handler.on_day_of_month(numeric_system::standard, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::standard, pad_type::space); + break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard); + handler.on_24_hour(numeric_system::standard, pad); break; case 'I': - handler.on_12_hour(numeric_system::standard); + handler.on_12_hour(numeric_system::standard, pad); break; case 'M': - handler.on_minute(numeric_system::standard); + handler.on_minute(numeric_system::standard, pad); break; case 'S': - handler.on_second(numeric_system::standard); + handler.on_second(numeric_system::standard, pad); break; // Other: case 'c': @@ -572,7 +848,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_duration_unit(); break; case 'z': - handler.on_utc_offset(); + handler.on_utc_offset(numeric_system::standard); break; case 'Z': handler.on_tz_name(); @@ -582,6 +858,15 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'Y': + handler.on_year(numeric_system::alternative); + break; + case 'y': + handler.on_offset_year(); + break; + case 'C': + handler.on_century(numeric_system::alternative); + break; case 'c': handler.on_datetime(numeric_system::alternative); break; @@ -591,6 +876,9 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); + break; default: FMT_THROW(format_error("invalid format")); } @@ -600,6 +888,27 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { + case 'y': + handler.on_short_year(numeric_system::alternative); + break; + case 'm': + handler.on_dec_month(numeric_system::alternative); + break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative, pad); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::alternative, pad_type::space); + break; case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; @@ -607,16 +916,19 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative); + handler.on_24_hour(numeric_system::alternative, pad); break; case 'I': - handler.on_12_hour(numeric_system::alternative); + handler.on_12_hour(numeric_system::alternative, pad); break; case 'M': - handler.on_minute(numeric_system::alternative); + handler.on_minute(numeric_system::alternative, pad); break; case 'S': - handler.on_second(numeric_system::alternative); + handler.on_second(numeric_system::alternative, pad); + break; + case 'z': + handler.on_utc_offset(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); @@ -631,75 +943,747 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, return ptr; } -struct chrono_format_checker { - FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } - - template void on_text(const Char*, const Char*) {} - FMT_NORETURN void on_abbr_weekday() { report_no_date(); } - FMT_NORETURN void on_full_weekday() { report_no_date(); } - FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } - FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } - FMT_NORETURN void on_abbr_month() { report_no_date(); } - FMT_NORETURN void on_full_month() { report_no_date(); } - void on_24_hour(numeric_system) {} - void on_12_hour(numeric_system) {} - void on_minute(numeric_system) {} - void on_second(numeric_system) {} - FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } - FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } - FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } - FMT_NORETURN void on_us_date() { report_no_date(); } - FMT_NORETURN void on_iso_date() { report_no_date(); } - void on_12_hour_time() {} - void on_24_hour_time() {} - void on_iso_time() {} - void on_am_pm() {} - void on_duration_value() {} - void on_duration_unit() {} - FMT_NORETURN void on_utc_offset() { report_no_date(); } - FMT_NORETURN void on_tz_name() { report_no_date(); } +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->unsupported(); + } + FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } + FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } + FMT_CONSTEXPR void on_full_weekday() { unsupported(); } + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_abbr_month() { unsupported(); } + FMT_CONSTEXPR void on_full_month() { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year() { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_us_date() { unsupported(); } + FMT_CONSTEXPR void on_iso_date() { unsupported(); } + FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } + FMT_CONSTEXPR void on_iso_time() { unsupported(); } + FMT_CONSTEXPR void on_am_pm() { unsupported(); } + FMT_CONSTEXPR void on_duration_value() { unsupported(); } + FMT_CONSTEXPR void on_duration_unit() { unsupported(); } + FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; -template ::value)> -inline bool isnan(T) { - return false; +struct tm_format_checker : null_chrono_spec_handler { + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_tz_name() {} +}; + +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; } -template ::value)> -inline bool isnan(T value) { - return std::isnan(value); +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; } -template ::value)> -inline bool isfinite(T) { - return true; +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; } -template ::value)> -inline bool isfinite(T value) { - return std::isfinite(value); +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; } -// Converts value to int and checks that it's in the range [0, upper). -template ::value)> -inline int to_nonnegative_int(T value, int upper) { - FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); - (void)upper; - return static_cast(value); +template +struct has_member_data_tm_gmtoff : std::false_type {}; +template +struct has_member_data_tm_gmtoff> + : std::true_type {}; + +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + +#if FMT_USE_TZSET +inline void tzset_once() { + static bool init = []() -> bool { + _tzset(); + return true; + }(); + ignore_unused(init); } -template ::value)> -inline int to_nonnegative_int(T value, int upper) { - FMT_ASSERT( - std::isnan(value) || (value >= 0 && value <= static_cast(upper)), - "invalid value"); - (void)upper; - return static_cast(value); +#endif + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return int_value; +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + + const auto fractional = d - fmt_duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : fmt_duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = detail::count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits).end; + } + } else if (precision > 0) { + *out++ = '.'; + leading_zeroes = (std::min)(leading_zeroes, precision); + int remaining = precision - leading_zeroes; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(detail::pow10(to_unsigned(num_truncated_digits))); + if (n) { + out = format_decimal(out, n, remaining).end; + } + return; + } + if (n) { + out = format_decimal(out, n, num_digits).end; + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the chrono_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + const bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + const auto prev_year = curr_year - 1; + const auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + const auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + const auto year = tm_year(); + const auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year) { + // At least 4 characters. + int width = 4; + if (year < 0) { + *out_++ = '-'; + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (width > num_digits) + out_ = detail::fill_n(out_, width - num_digits, '0'); + out_ = format_decimal(out_, n, num_digits).end; + } + void write_year(long long year) { + if (year >= 0 && year < 10000) { + write2(static_cast(year / 100)); + write2(static_cast(year % 100)); + } else { + write_year_extended(year); + } + } + + void write_utc_offset(long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset_impl(const T& tm, numeric_system ns) { +#if defined(_WIN32) && defined(_UCRT) +# if FMT_USE_TZSET + tzset_once(); +# endif + long offset = 0; + _get_timezone(&offset); + if (tm.tm_isdst) { + long dstbias = 0; + _get_dstbias(&dstbias); + offset += dstbias; + } + write_utc_offset(-offset, ns); +#else + if (ns == numeric_system::standard) return format_localized('z'); + + // Extract timezone offset from timezone conversion functions. + std::tm gtm = tm; + std::time_t gt = std::mktime(>m); + std::tm ltm = gmtime(gt); + std::time_t lt = std::mktime(<m); + long offset = gt - lt; + write_utc_offset(offset, ns); +#endif + } + + template ::value)> + void format_tz_name_impl(const T& tm) { + if (is_classic_) + out_ = write_tm_str(out_, tm.tm_zone, loc_); + else + format_localized('Z'); + } + template ::value)> + void format_tz_name_impl(const T&) { + format_localized('Z'); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month(numeric_system::standard, pad_type::space); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + copy2(buf, digits2(static_cast(year / 100))); + } else { + offset = 4; + write_year_extended(year); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_tz_name() { format_tz_name_impl(tm_); } + + void on_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year()); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week, + pad); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year(), pad); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { write_year(tm_iso_week_year()); } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year() { + auto yday = tm_yday() + 1; + write1(yday / 100); + write2(yday % 100); + } + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = std::copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year() {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) { + FMT_THROW(format_error("precision not allowed for this argument type")); + } + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return x % static_cast(y); } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return std::fmod(x, static_cast(y)); } @@ -714,67 +1698,52 @@ template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template -To fmt_safe_duration_cast(std::chrono::duration from) { - int ec; - To to = safe_duration_cast::safe_duration_cast(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; - const auto d_as_common = fmt_safe_duration_cast(d); + const auto d_as_common = fmt_duration_cast(d); const auto d_as_whole_seconds = - fmt_safe_duration_cast(d_as_common); + fmt_duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = - fmt_safe_duration_cast>(diff); + fmt_duration_cast>(diff); return ms; #else - auto s = std::chrono::duration_cast(d); - return std::chrono::duration_cast(d - s); + auto s = fmt_duration_cast(d); + return fmt_duration_cast(d - s); #endif } -template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { - using common_type = typename std::common_type::type; - auto ms = mod(d.count() * static_cast(Period::num) / - static_cast(Period::den) * 1000, - 1000); - return std::chrono::duration(static_cast(ms)); +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { + return write(out, val); } -template -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0}; - if (precision >= 0) return format_to(out, pr_f, val, precision); - const Char fp_f[] = {'{', ':', 'g', '}', 0}; - const Char format[] = {'{', '}', 0}; - return format_to(out, std::is_floating_point::value ? fp_f : format, - val); +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); + specs.precision = precision; + specs.type = + precision >= 0 ? presentation_type::fixed : presentation_type::general; + return write(out, val, specs); } + template -OutputIt copy_unit(string_view unit, OutputIt out, Char) { +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { return std::copy(unit.begin(), unit.end(), out); } template -OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); @@ -782,21 +1751,49 @@ OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { } template -OutputIt format_duration_unit(OutputIt out) { +auto format_duration_unit(OutputIt out) -> OutputIt { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); - const Char num_f[] = {'[', '{', '}', ']', 's', 0}; - if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num); - const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; - return format_to(out, num_def_f, Period::num, Period::den); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; } +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + get_locale(bool localized, locale_ref loc) : has_locale_(localized) { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + if (localized) + ::new (&locale_) std::locale(loc.template get()); +#endif + } + ~get_locale() { + if (has_locale_) locale_.~locale(); + } + operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + template struct chrono_formatter { FormatContext& context; OutputIt out; int precision; + bool localized = false; // rep is unsigned to avoid overflow. using rep = conditional_t::value && sizeof(Rep) < sizeof(int), @@ -808,9 +1805,10 @@ struct chrono_formatter { bool negative; using char_type = typename FormatContext::char_type; + using tm_writer_type = tm_writer; - explicit chrono_formatter(FormatContext& ctx, OutputIt o, - std::chrono::duration d) + chrono_formatter(FormatContext& ctx, OutputIt o, + std::chrono::duration d) : context(ctx), out(o), val(static_cast(d.count())), @@ -822,18 +1820,12 @@ struct chrono_formatter { // this may overflow and/or the result may not fit in the // target type. -#if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration(val); - s = fmt_safe_duration_cast(tmpval); -#else - s = std::chrono::duration_cast( - std::chrono::duration(val)); -#endif + s = fmt_duration_cast(std::chrono::duration(val)); } // returns true if nan or inf, writes to out. - bool handle_nan_inf() { + auto handle_nan_inf() -> bool { if (isfinite(val)) { return false; } @@ -850,17 +1842,22 @@ struct chrono_formatter { return true; } - Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); } + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } - Rep hour12() const { + auto hour12() const -> Rep { Rep hour = static_cast(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } - Rep minute() const { return static_cast(mod((s.count() / 60), 60)); } - Rep second() const { return static_cast(mod(s.count(), 60)); } + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } - std::tm time() const { + auto time() const -> std::tm { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); @@ -875,13 +1872,15 @@ struct chrono_formatter { } } - void write(Rep value, int width) { + void write(Rep value, int width, pad_type pad = pad_type::zero) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } out = format_decimal(out, n, num_digits).end; } @@ -889,15 +1888,13 @@ struct chrono_formatter { void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } - void format_localized(const tm& time, char format, char modifier = 0) { + template + void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); - auto locale = context.locale().template get(); - auto& facet = std::use_facet>(locale); - std::basic_ostringstream os; - os.imbue(locale); - facet.put(os, os, ' ', &time, format, modifier); - auto str = os.str(); - std::copy(str.begin(), str.end(), out); + get_locale loc(localized, context.locale()); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); } void on_text(const char_type* begin, const char_type* end) { @@ -916,64 +1913,80 @@ struct chrono_formatter { void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} - void on_utc_offset() {} + void on_utc_offset(numeric_system) {} void on_tz_name() {} + void on_year(numeric_system) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year() { + if (handle_nan_inf()) return; + write(days(), 0); + } - void on_24_hour(numeric_system ns) { + void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2); + if (ns == numeric_system::standard) return write(hour(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_localized(time, 'H', 'O'); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); } - void on_12_hour(numeric_system ns) { + void on_12_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2); + if (ns == numeric_system::standard) return write(hour12(), 2, pad); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_localized(time, 'I', 'O'); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); } - void on_minute(numeric_system ns) { + void on_minute(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2); + if (ns == numeric_system::standard) return write(minute(), 2, pad); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_localized(time, 'M', 'O'); + format_tm(time, &tm_writer_type::on_minute, ns, pad); } - void on_second(numeric_system ns) { + void on_second(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { - write(second(), 2); -#if FMT_SAFE_DURATION_CAST - // convert rep->Rep - using duration_rep = std::chrono::duration; - using duration_Rep = std::chrono::duration; - auto tmpval = fmt_safe_duration_cast(duration_rep{val}); -#else - auto tmpval = std::chrono::duration(val); -#endif - auto ms = get_milliseconds(tmpval); - if (ms != std::chrono::milliseconds(0)) { - *out++ = '.'; - write(ms.count(), 3); + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') { + out = detail::write_padding(out, pad); + } + out = std::copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_localized(time, 'S', 'O'); + format_tm(time, &tm_writer_type::on_second, ns, pad); } void on_12_hour_time() { if (handle_nan_inf()) return; - format_localized(time(), 'r'); + format_tm(time(), &tm_writer_type::on_12_hour_time); } void on_24_hour_time() { @@ -992,12 +2005,12 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - write(second(), 2); + on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { if (handle_nan_inf()) return; - format_localized(time(), 'p'); + format_tm(time(), &tm_writer_type::on_am_pm); } void on_duration_value() { @@ -1010,114 +2023,410 @@ struct chrono_formatter { out = format_duration_unit(out); } }; + } // namespace detail -template -struct formatter, Char> { +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; +#else +// A fallback version of weekday. +class weekday { private: - basic_format_specs specs; - int precision; - using arg_ref_type = detail::arg_ref; - arg_ref_type width_ref; - arg_ref_type precision_ref; - mutable basic_string_view format_str; - using duration = std::chrono::duration; - - struct spec_handler { - formatter& f; - basic_format_parse_context& context; - basic_string_view format_str; - - template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } + unsigned char value_; - FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } + public: + weekday() = default; + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } +}; - FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { - return arg_ref_type(context.next_arg_id()); - } +class day { + private: + unsigned char value_; - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - void on_fill(basic_string_view fill) { f.specs.fill = fill; } - void on_align(align_t align) { f.specs.align = align; } - void on_width(int width) { f.specs.width = width; } - void on_precision(int _precision) { f.precision = _precision; } - void end_precision() {} + public: + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; - template void on_dynamic_width(Id arg_id) { - f.width_ref = make_arg_ref(arg_id); - } +class month { + private: + unsigned char value_; - template void on_dynamic_precision(Id arg_id) { - f.precision_ref = make_arg_ref(arg_id); + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } +}; + +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; +#endif + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - }; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } - using iterator = typename basic_format_parse_context::iterator; - struct parse_range { - iterator begin; - iterator end; - }; + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_wday = static_cast(wd.c_encoding()); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); + } +}; - FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end || *begin == '}') return {begin, begin}; - spec_handler handler{*this, ctx, format_str}; - begin = detail::parse_align(begin, end, handler); - if (begin == end) return {begin, begin}; - begin = detail::parse_width(begin, end, handler); - if (begin == end) return {begin, begin}; - if (*begin == '.') { - if (std::is_floating_point::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - end = parse_chrono_format(begin, end, detail::chrono_format_checker()); - return {begin, end}; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + public: - formatter() : precision(-1) {} + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard); + return w.out(); + } +}; +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - auto range = do_parse(ctx); - format_str = basic_string_view( - &*range.begin, detail::to_unsigned(range.end - range.begin)); - return range.end; + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } template - auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { - auto begin = format_str.begin(), end = format_str.end(); + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + bool localized_ = false; + basic_string_view format_str_; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_.precision, precision_ref_, + ctx); + } + if (it != end && *it == 'L') { + localized_ = true; + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + format_str_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(std::chrono::duration d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = format_str_.begin(), end = format_str_.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. - basic_memory_buffer buf; + auto buf = basic_memory_buffer(); auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs.width, width_ref, + detail::handle_dynamic_spec(specs.width, width_ref_, ctx); detail::handle_dynamic_spec(precision, - precision_ref, ctx); + precision_ref_, ctx); if (begin == end || *begin == '}') { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); } else { - detail::chrono_formatter f( - ctx, out, d); + using chrono_formatter = + detail::chrono_formatter; + auto f = chrono_formatter(ctx, out, d); f.precision = precision; - parse_chrono_format(begin, end, f); + f.localized = localized_; + detail::parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template +struct formatter, + Char> : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = + detail::fmt_duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) + --tm.tm_sec; + else + tm = gmtime(val - second); + subsecs += detail::fmt_duration_cast(std::chrono::seconds(1)); + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +#if FMT_USE_LOCAL_TIME +template +struct formatter, Char> + : formatter { + FMT_CONSTEXPR formatter() { + this->format_str_ = detail::string_literal{}; + } + + template + auto format(std::chrono::local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num != 1 || period::den != 1 || + std::is_floating_point::value) { + const auto epoch = val.time_since_epoch(); + const auto subsecs = detail::fmt_duration_cast( + epoch - detail::fmt_duration_cast(epoch)); + + return formatter::do_format(localtime(val), ctx, &subsecs); } + + return formatter::format(localtime(val), ctx); + } +}; +#endif + +#if FMT_USE_UTC_TIME +template +struct formatter, + Char> + : formatter, + Char> { + template + auto format(std::chrono::time_point val, + FormatContext& ctx) const -> decltype(ctx.out()) { + return formatter< + std::chrono::time_point, + Char>::format(std::chrono::utc_clock::to_sys(val), ctx); + } +}; +#endif + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + + protected: + basic_string_view format_str_; + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = std::back_inserter(buf); + detail::handle_dynamic_spec(specs.width, width_ref_, + ctx); + + auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = + detail::tm_writer(loc, out, tm, subsecs); + detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + if (it == end) return it; + + end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + // Replace the default format_str only if the new spec is not empty. + if (end != it) format_str_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); + } }; +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/external/fmt/include/fmt/color.h b/external/fmt/include/fmt/color.h index b65f892afc..f0e9dd94ef 100644 --- a/external/fmt/include/fmt/color.h +++ b/external/fmt/include/fmt/color.h @@ -11,6 +11,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -177,9 +178,13 @@ enum class terminal_color : uint8_t { enum class emphasis : uint8_t { bold = 1, - italic = 1 << 1, - underline = 1 << 2, - strikethrough = 1 << 3 + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, }; // rgb is a struct for red, green and blue colors. @@ -202,17 +207,16 @@ namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { - FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} - FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), - value{} { + FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} + FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = static_cast(rgb_color); } - FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} { + FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { value.rgb_color = (static_cast(rgb_color.r) << 16) | (static_cast(rgb_color.g) << 8) | rgb_color.b; } - FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(), - value{} { + FMT_CONSTEXPR color_type(terminal_color term_color) noexcept + : is_rgb(), value{} { value.term_color = static_cast(term_color); } bool is_rgb; @@ -223,21 +227,19 @@ struct color_type { }; } // namespace detail -// Experimental text formatting support. +/// A text style consisting of foreground and background colors and emphasis. class text_style { public: - FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT - : set_foreground_color(), - set_background_color(), - ems(em) {} + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : set_foreground_color(), set_background_color(), ems(em) {} - FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { if (!set_foreground_color) { set_foreground_color = rhs.set_foreground_color; foreground_color = rhs.foreground_color; } else if (rhs.set_foreground_color) { if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; } @@ -246,7 +248,7 @@ class text_style { background_color = rhs.background_color; } else if (rhs.set_background_color) { if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); background_color.value.rgb_color |= rhs.background_color.value.rgb_color; } @@ -255,68 +257,37 @@ class text_style { return *this; } - friend FMT_CONSTEXPR text_style operator|(text_style lhs, - const text_style& rhs) { + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { return lhs |= rhs; } - FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { - if (!set_foreground_color) { - set_foreground_color = rhs.set_foreground_color; - foreground_color = rhs.foreground_color; - } else if (rhs.set_foreground_color) { - if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; - } - - if (!set_background_color) { - set_background_color = rhs.set_background_color; - background_color = rhs.background_color; - } else if (rhs.set_background_color) { - if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't AND a terminal color")); - background_color.value.rgb_color &= rhs.background_color.value.rgb_color; - } - - ems = static_cast(static_cast(ems) & - static_cast(rhs.ems)); - return *this; - } - - friend FMT_CONSTEXPR text_style operator&(text_style lhs, - const text_style& rhs) { - return lhs &= rhs; - } - - FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return set_foreground_color; } - FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_background() const noexcept -> bool { return set_background_color; } - FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return static_cast(ems) != 0; } - FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } - FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return ems; } private: FMT_CONSTEXPR text_style(bool is_foreground, - detail::color_type text_color) FMT_NOEXCEPT - : set_foreground_color(), - set_background_color(), - ems() { + detail::color_type text_color) noexcept + : set_foreground_color(), set_background_color(), ems() { if (is_foreground) { foreground_color = text_color; set_foreground_color = true; @@ -326,10 +297,11 @@ class text_style { } } - friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) - FMT_NOEXCEPT; - friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) - FMT_NOEXCEPT; + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; detail::color_type foreground_color; detail::color_type background_color; @@ -338,15 +310,20 @@ class text_style { emphasis ems; }; -FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT { - return text_style(/*is_foreground=*/true, foreground); +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return text_style(true, foreground); } -FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT { - return text_style(/*is_foreground=*/false, background); +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return text_style(false, background); } -FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { return text_style(lhs) | rhs; } @@ -354,11 +331,11 @@ namespace detail { template struct ansi_color_escape { FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, - const char* esc) FMT_NOEXCEPT { + const char* esc) noexcept { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { - bool is_background = esc == detail::data::background_color; + bool is_background = esc == string_view("\x1b[48;2;"); uint32_t value = text_color.value.term_color; // Background ASCII codes are the same as the foreground ones but with // 10 more. @@ -389,17 +366,19 @@ template struct ansi_color_escape { to_esc(color.b, buffer + 15, 'm'); buffer[19] = static_cast(0); } - FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { - uint8_t em_codes[4] = {}; - uint8_t em_bits = static_cast(em); - if (em_bits & static_cast(emphasis::bold)) em_codes[0] = 1; - if (em_bits & static_cast(emphasis::italic)) em_codes[1] = 3; - if (em_bits & static_cast(emphasis::underline)) em_codes[2] = 4; - if (em_bits & static_cast(emphasis::strikethrough)) - em_codes[3] = 9; + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; size_t index = 0; - for (int i = 0; i < 4; ++i) { + for (size_t i = 0; i < num_emphases; ++i) { if (!em_codes[i]) continue; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); @@ -408,71 +387,63 @@ template struct ansi_color_escape { } buffer[index++] = static_cast(0); } - FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } - FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } - FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { - return buffer + std::char_traits::length(buffer); + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); } private: - Char buffer[7u + 3u * 4u + 1u]; + static constexpr size_t num_emphases = 8; + Char buffer[7u + 3u * num_emphases + 1u]; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, - char delimiter) FMT_NOEXCEPT { + char delimiter) noexcept { out[0] = static_cast('0' + c / 100); out[1] = static_cast('0' + c / 10 % 10); out[2] = static_cast('0' + c % 10); out[3] = static_cast(delimiter); } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } }; template -FMT_CONSTEXPR ansi_color_escape make_foreground_color( - detail::color_type foreground) FMT_NOEXCEPT { - return ansi_color_escape(foreground, detail::data::foreground_color); +FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_background_color( - detail::color_type background) FMT_NOEXCEPT { - return ansi_color_escape(background, detail::data::background_color); +FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) FMT_NOEXCEPT { +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { return ansi_color_escape(em); } -template -inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { - std::fputs(chars, stream); -} - -template <> -inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { - std::fputws(chars, stream); +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); } -template inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(detail::data::reset_color, stream); -} - -template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(detail::data::wreset_color, stream); -} - -template -inline void reset_color(basic_memory_buffer& buffer) FMT_NOEXCEPT { - const char* begin = data::reset_color; - const char* end = begin + sizeof(data::reset_color) - 1; - buffer.append(begin, end); -} +template struct styled_arg : detail::view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; template -void vformat_to(basic_memory_buffer& buf, const text_style& ts, - basic_string_view format_str, - basic_format_args> args) { +void vformat_to( + buffer& buf, const text_style& ts, basic_string_view format_str, + basic_format_args>> args) { bool has_style = false; if (ts.has_emphasis()) { has_style = true; @@ -489,78 +460,153 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts, auto background = detail::make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - detail::vformat_to(buf, format_str, args); + detail::vformat_to(buf, format_str, args, {}); if (has_style) detail::reset_color(buf); } + } // namespace detail -template > -void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, to_string_view(format), args); - buf.push_back(Char(0)); - detail::fputs(buf.data(), f); +inline void vprint(FILE* f, const text_style& ts, string_view fmt, + format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); } /** - Formats a string and prints it to the specified file stream using ANSI - escape sequences to specify text formatting. - Example: - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(std::FILE* f, const text_style& ts, const S& format_str, - const Args&... args) { - detail::check_format_string(format_str); - using context = buffer_context>; - format_arg_store as{args...}; - vprint(f, ts, format_str, basic_format_args(as)); +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt, fmt::make_format_args(args...)); } /** - Formats a string and prints it to stdout using ANSI escape sequences to - specify text formatting. - Example: - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(const text_style& ts, const S& format_str, const Args&... args) { - return print(stdout, ts, format_str, args...); +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); } -template > -inline std::basic_string vformat( - const text_style& ts, const S& format_str, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, to_string_view(format_str), args); +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); return fmt::to_string(buf); } /** - \rst - Formats arguments and returns the result as a string using ANSI - escape sequences to specify text formatting. - - **Example**:: - - #include - std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}", 42); - \endrst -*/ -template > -inline std::basic_string format(const text_style& ts, const S& format_str, - const Args&... args) { - return vformat(ts, to_string_view(format_str), - detail::make_args_checked(format_str, args...)); + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt, fmt::make_format_args(args...)); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); +} + +/** + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt, fmt::make_format_args(args...)); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + const auto& value = arg.value; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = std::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = std::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = std::copy(background.begin(), background.end(), out); + } + out = formatter::format(value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = std::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; } +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/external/fmt/include/fmt/compile.h b/external/fmt/include/fmt/compile.h index d7e6449ebb..b2afc2c309 100644 --- a/external/fmt/include/fmt/compile.h +++ b/external/fmt/include/fmt/compile.h @@ -8,378 +8,86 @@ #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ -#include +#ifndef FMT_MODULE +# include // std::back_inserter +#endif #include "format.h" FMT_BEGIN_NAMESPACE -namespace detail { // A compile-time string which is compiled into fast formatting code. -class compiled_string {}; - -template -struct is_compiled_string : std::is_base_of {}; - -/** - \rst - Converts a string literal *s* into a format string that will be parsed at - compile time and converted into efficient formatting code. Requires C++17 - ``constexpr if`` compiler support. - - **Example**:: - - // Converts 42 into std::string using the most efficient method and no - // runtime format string processing. - std::string s = fmt::format(FMT_COMPILE("{}"), 42); - \endrst - */ -#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string) - -template -const T& first(const T& value, const Tail&...) { - return value; -} - -// Part of a compiled format string. It can be either literal text or a -// replacement field. -template struct format_part { - enum class kind { arg_index, arg_name, text, replacement }; - - struct replacement { - arg_ref arg_id; - dynamic_format_specs specs; - }; - - kind part_kind; - union value { - int arg_index; - basic_string_view str; - replacement repl; - - FMT_CONSTEXPR value(int index = 0) : arg_index(index) {} - FMT_CONSTEXPR value(basic_string_view s) : str(s) {} - FMT_CONSTEXPR value(replacement r) : repl(r) {} - } val; - // Position past the end of the argument id. - const Char* arg_id_end = nullptr; - - FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {}) - : part_kind(k), val(v) {} - - static FMT_CONSTEXPR format_part make_arg_index(int index) { - return format_part(kind::arg_index, index); - } - static FMT_CONSTEXPR format_part make_arg_name(basic_string_view name) { - return format_part(kind::arg_name, name); - } - static FMT_CONSTEXPR format_part make_text(basic_string_view text) { - return format_part(kind::text, text); - } - static FMT_CONSTEXPR format_part make_replacement(replacement repl) { - return format_part(kind::replacement, repl); - } -}; - -template struct part_counter { - unsigned num_parts = 0; - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - if (begin != end) ++num_parts; - } - - FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(basic_string_view) { - return ++num_parts, 0; - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - - FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, - const Char* end) { - // Find the matching brace. - unsigned brace_counter = 0; - for (; begin != end; ++begin) { - if (*begin == '{') { - ++brace_counter; - } else if (*begin == '}') { - if (brace_counter == 0u) break; - --brace_counter; - } - } - return begin; - } - - FMT_CONSTEXPR void on_error(const char*) {} -}; - -// Counts the number of parts in a format string. -template -FMT_CONSTEXPR unsigned count_parts(basic_string_view format_str) { - part_counter counter; - parse_format_string(format_str, counter); - return counter.num_parts; -} - -template -class format_string_compiler : public error_handler { - private: - using part = format_part; - - PartHandler handler_; - part part_; - basic_string_view format_str_; - basic_format_parse_context parse_context_; - - public: - FMT_CONSTEXPR format_string_compiler(basic_string_view format_str, - PartHandler handler) - : handler_(handler), - format_str_(format_str), - parse_context_(format_str) {} - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - if (begin != end) - handler_(part::make_text({begin, to_unsigned(end - begin)})); - } - - FMT_CONSTEXPR int on_arg_id() { - part_ = part::make_arg_index(parse_context_.next_arg_id()); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(int id) { - parse_context_.check_arg_id(id); - part_ = part::make_arg_index(id); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(basic_string_view id) { - part_ = part::make_arg_name(id); - return 0; - } +FMT_EXPORT class compiled_string {}; - FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) { - part_.arg_id_end = ptr; - handler_(part_); - } - - FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, - const Char* end) { - auto repl = typename part::replacement(); - dynamic_specs_handler> handler( - repl.specs, parse_context_); - auto it = parse_format_specs(begin, end, handler); - if (*it != '}') on_error("missing '}' in format string"); - repl.arg_id = part_.part_kind == part::kind::arg_index - ? arg_ref(part_.val.arg_index) - : arg_ref(part_.val.str); - auto part = part::make_replacement(repl); - part.arg_id_end = begin; - handler_(part); - return it; - } -}; - -// Compiles a format string and invokes handler(part) for each parsed part. -template -FMT_CONSTEXPR void compile_format_string(basic_string_view format_str, - PartHandler handler) { - parse_format_string( - format_str, - format_string_compiler(format_str, handler)); -} - -template -void format_arg( - basic_format_parse_context& parse_ctx, - Context& ctx, Id arg_id) { - ctx.advance_to(visit_format_arg( - arg_formatter(ctx, &parse_ctx), - ctx.arg(arg_id))); -} - -// vformat_to is defined in a subnamespace to prevent ADL. -namespace cf { -template -auto vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> typename Context::iterator { - using char_type = typename Context::char_type; - basic_format_parse_context parse_ctx( - to_string_view(cf.format_str_)); - Context ctx(out, args); - - const auto& parts = cf.parts(); - for (auto part_it = std::begin(parts); part_it != std::end(parts); - ++part_it) { - const auto& part = *part_it; - const auto& value = part.val; - - using format_part_t = format_part; - switch (part.part_kind) { - case format_part_t::kind::text: { - const auto text = value.str; - auto output = ctx.out(); - auto&& it = reserve(output, text.size()); - it = std::copy_n(text.begin(), text.size(), it); - ctx.advance_to(output); - break; - } - - case format_part_t::kind::arg_index: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.arg_index); - break; - - case format_part_t::kind::arg_name: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.str); - break; - - case format_part_t::kind::replacement: { - const auto& arg_id_value = value.repl.arg_id.val; - const auto arg = value.repl.arg_id.kind == arg_id_kind::index - ? ctx.arg(arg_id_value.index) - : ctx.arg(arg_id_value.name); - - auto specs = value.repl.specs; - - handle_dynamic_spec(specs.width, specs.width_ref, ctx); - handle_dynamic_spec(specs.precision, - specs.precision_ref, ctx); - - error_handler h; - numeric_specs_checker checker(h, arg.type()); - if (specs.align == align::numeric) checker.require_numeric_argument(); - if (specs.sign != sign::none) checker.check_sign(); - if (specs.alt) checker.require_numeric_argument(); - if (specs.precision >= 0) checker.check_precision(); - - advance_to(parse_ctx, part.arg_id_end); - ctx.advance_to( - visit_format_arg(arg_formatter( - ctx, nullptr, &specs), - arg)); - break; - } - } - } - return ctx.out(); -} -} // namespace cf - -struct basic_compiled_format {}; - -template -struct compiled_format_base : basic_compiled_format { - using char_type = char_t; - using parts_container = std::vector>; - - parts_container compiled_parts; - - explicit compiled_format_base(basic_string_view format_str) { - compile_format_string(format_str, - [this](const format_part& part) { - compiled_parts.push_back(part); - }); - } - - const parts_container& parts() const { return compiled_parts; } -}; - -template struct format_part_array { - format_part data[N] = {}; - FMT_CONSTEXPR format_part_array() = default; -}; - -template -FMT_CONSTEXPR format_part_array compile_to_parts( - basic_string_view format_str) { - format_part_array parts; - unsigned counter = 0; - // This is not a lambda for compatibility with older compilers. - struct { - format_part* parts; - unsigned* counter; - FMT_CONSTEXPR void operator()(const format_part& part) { - parts[(*counter)++] = part; - } - } collector{parts.data, &counter}; - compile_format_string(format_str, collector); - if (counter < N) { - parts.data[counter] = - format_part::make_text(basic_string_view()); - } - return parts; -} +namespace detail { -template constexpr const T& constexpr_max(const T& a, const T& b) { - return (a < b) ? b : a; +template +FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it) + -> counting_iterator { + return it + (end - begin); } template -struct compiled_format_base::value>> - : basic_compiled_format { - using char_type = char_t; - - FMT_CONSTEXPR explicit compiled_format_base(basic_string_view) {} +struct is_compiled_string : std::is_base_of {}; -// Workaround for old compilers. Format string compilation will not be -// performed there anyway. -#if FMT_USE_CONSTEXPR - static FMT_CONSTEXPR_DECL const unsigned num_format_parts = - constexpr_max(count_parts(to_string_view(S())), 1u); +/** + * Converts a string literal `s` into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires C++17 + * `constexpr if` compiler support. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); + */ +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit) #else - static const unsigned num_format_parts = 1; +# define FMT_COMPILE(s) FMT_STRING(s) #endif - using parts_container = format_part[num_format_parts]; - - const parts_container& parts() const { - static FMT_CONSTEXPR_DECL const auto compiled_parts = - compile_to_parts( - detail::to_string_view(S())); - return compiled_parts.data; +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct udl_compiled_string : compiled_string { + using char_type = Char; + explicit constexpr operator basic_string_view() const { + return {Str.data, N - 1}; } }; +#endif -template -class compiled_format : private compiled_format_base { - public: - using typename compiled_format_base::char_type; - - private: - basic_string_view format_str_; - - template - friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> - typename Context::iterator; - - public: - compiled_format() = delete; - explicit constexpr compiled_format(basic_string_view format_str) - : compiled_format_base(format_str), format_str_(format_str) {} -}; +template +auto first(const T& value, const Tail&...) -> const T& { + return value; +} -#ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template struct type_list {}; // Returns a reference to the argument at index N from [first, rest...]. template -constexpr const auto& get(const T& first, const Args&... rest) { +constexpr const auto& get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) { static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); if constexpr (N == 0) return first; else - return get(rest...); + return detail::get(rest...); +} + +template +constexpr int get_arg_index_by_name(basic_string_view name, + type_list) { + return get_arg_index_by_name(name); } template struct get_type_impl; template struct get_type_impl> { - using type = remove_cvref_t(std::declval()...))>; + using type = + remove_cvref_t(std::declval()...))>; }; template @@ -392,7 +100,7 @@ template struct text { using char_type = Char; template - OutputIt format(OutputIt out, const Args&...) const { + constexpr OutputIt format(OutputIt out, const Args&...) const { return write(out, data); } }; @@ -406,14 +114,42 @@ constexpr text make_text(basic_string_view s, size_t pos, return {{&s[pos], size}}; } +template struct code_unit { + Char value; + using char_type = Char; + + template + constexpr OutputIt format(OutputIt out, const Args&...) const { + *out++ = value; + return out; + } +}; + +// This ensures that the argument type is convertible to `const T&`. +template +constexpr const T& get_arg_checked(const Args&... args) { + const auto& arg = detail::get(args...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + +template +struct is_compiled_format> : std::true_type {}; + // A replacement field that refers to argument N. template struct field { using char_type = Char; template - OutputIt format(OutputIt out, const Args&... args) const { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); + constexpr OutputIt format(OutputIt out, const Args&... args) const { + const T& arg = get_arg_checked(args...); + if constexpr (std::is_convertible>::value) { + auto s = basic_string_view(arg); + return copy(s.begin(), s.end(), out); + } return write(out, arg); } }; @@ -421,17 +157,50 @@ template struct field { template struct is_compiled_format> : std::true_type {}; +// A replacement field that refers to argument with name. +template struct runtime_named_field { + using char_type = Char; + basic_string_view name; + + template + constexpr static bool try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view arg_name, const T& arg) { + if constexpr (is_named_arg::type>::value) { + if (arg_name == arg.name) { + out = write(out, arg.value); + return true; + } + } + return false; + } + + template + constexpr OutputIt format(OutputIt out, const Args&... args) const { + bool found = (try_format_argument(out, name, args) || ...); + if (!found) { + FMT_THROW(format_error("argument with specified name is not found")); + } + return out; + } +}; + +template +struct is_compiled_format> : std::true_type {}; + // A replacement field that refers to argument N and has format specifiers. template struct spec_field { using char_type = Char; - mutable formatter fmt; + formatter fmt; template - OutputIt format(OutputIt out, const Args&... args) const { - // This ensures that the argument type is convertile to `const T&`. - const T& arg = get(args...); - basic_format_context ctx(out, {}); - return fmt.format(arg, ctx); + constexpr FMT_INLINE OutputIt format(OutputIt out, + const Args&... args) const { + const auto& vargs = + fmt::make_format_args>(args...); + basic_format_context ctx(out, vargs); + return fmt.format(get_arg_checked(args...), ctx); } }; @@ -444,7 +213,7 @@ template struct concat { using char_type = typename L::char_type; template - OutputIt format(OutputIt out, const Args&... args) const { + constexpr OutputIt format(OutputIt out, const Args&... args) const { out = lhs.format(out, args...); return rhs.format(out, args...); } @@ -469,13 +238,12 @@ constexpr size_t parse_text(basic_string_view str, size_t pos) { } template -constexpr auto compile_format_string(S format_str); +constexpr auto compile_format_string(S fmt); template -constexpr auto parse_tail(T head, S format_str) { - if constexpr (POS != - basic_string_view(format_str).size()) { - constexpr auto tail = compile_format_string(format_str); +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); if constexpr (std::is_same, unknown_format>()) return tail; @@ -489,177 +257,273 @@ constexpr auto parse_tail(T head, S format_str) { template struct parse_specs_result { formatter fmt; size_t end; + int next_arg_id; }; +enum { manual_indexing_id = -1 }; + template constexpr parse_specs_result parse_specs(basic_string_view str, - size_t pos) { + size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = basic_format_parse_context(str); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); auto f = formatter(); auto end = f.parse(ctx); - return {f, pos + (end - str.data()) + 1}; + return {f, pos + fmt::detail::to_unsigned(end - str.data()), + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template struct arg_id_handler { + arg_ref arg_id; + + constexpr int on_auto() { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + constexpr int on_index(int id) { + arg_id = arg_ref(id); + return 0; + } + constexpr int on_name(basic_string_view id) { + arg_id = arg_ref(id); + return 0; + } +}; + +template struct parse_arg_id_result { + arg_ref arg_id; + const Char* arg_id_end; +}; + +template +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler{arg_ref{}}; + auto arg_id_end = parse_arg_id(begin, end, handler); + return parse_arg_id_result{handler.arg_id, arg_id_end}; +} + +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), fmt); + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + fmt); + } + } } // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template -constexpr auto compile_format_string(S format_str) { +constexpr auto compile_format_string(S fmt) { using char_type = typename S::char_type; - constexpr basic_string_view str = format_str; + constexpr auto str = basic_string_view(fmt); if constexpr (str[POS] == '{') { - if (POS + 1 == str.size()) - throw format_error("unmatched '{' in format string"); + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { - return parse_tail(make_text(str, POS, 1), format_str); - } else if constexpr (str[POS + 1] == '}') { - using type = get_type; - return parse_tail(field(), - format_str); - } else if constexpr (str[POS + 1] == ':') { - using type = get_type; - constexpr auto result = parse_specs(str, POS + 2); - return parse_tail( - spec_field{result.fmt}, format_str); + return parse_tail(make_text(str, POS, 1), fmt); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>(fmt); } else { - return unknown_format(); + constexpr auto arg_id_result = + parse_arg_id(str.data() + POS + 1, str.data() + str.size()); + constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); + constexpr char_type c = + arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); + static_assert(c == '}' || c == ':', "missing '}' in format string"); + if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { + static_assert( + ID == manual_indexing_id || ID == 0, + "cannot switch from automatic to manual argument indexing"); + constexpr auto arg_index = arg_id_result.arg_id.val.index; + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + fmt); + } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + if constexpr (arg_index >= 0) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(fmt); + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.val.name}, + fmt); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } } } else if constexpr (str[POS] == '}') { - if (POS + 1 == str.size()) - throw format_error("unmatched '}' in format string"); - return parse_tail(make_text(str, POS, 1), format_str); + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '}' in format string")); + return parse_tail(make_text(str, POS, 1), fmt); } else { constexpr auto end = parse_text(str, POS + 1); - return parse_tail(make_text(str, POS, end - POS), - format_str); + if constexpr (end - POS > 1) { + return parse_tail(make_text(str, POS, end - POS), fmt); + } else { + return parse_tail(code_unit{str[POS]}, fmt); + } } } template ::value || - detail::is_compiled_string::value)> -constexpr auto compile(S format_str) { - constexpr basic_string_view str = format_str; + FMT_ENABLE_IF(detail::is_compiled_string::value)> +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = - detail::compile_format_string, 0, 0>( - format_str); - if constexpr (std::is_same, - detail::unknown_format>()) { - return detail::compiled_format(to_string_view(format_str)); - } else { - return result; - } + detail::compile_format_string, 0, 0>(fmt); + return result; } } -#else -template ::value)> -constexpr auto compile(S format_str) -> detail::compiled_format { - return detail::compiled_format(to_string_view(format_str)); -} -#endif // __cpp_if_constexpr - -// Compiles the format string which must be a string literal. -template -auto compile(const Char (&format_str)[N]) - -> detail::compiled_format { - return detail::compiled_format( - basic_string_view(format_str, N - 1)); -} +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail -// DEPRECATED! use FMT_COMPILE instead. -template -FMT_DEPRECATED auto compile(const Args&... args) - -> decltype(detail::compile(args...)) { - return detail::compile(args...); -} +FMT_BEGIN_EXPORT -#if FMT_USE_CONSTEXPR -# ifdef __cpp_if_constexpr +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) template ::value)> FMT_INLINE std::basic_string format(const CompiledFormat& cf, const Args&... args) { - basic_memory_buffer buffer; - detail::buffer& base = buffer; - cf.format(std::back_inserter(base), args...); - return to_string(buffer); + auto s = std::basic_string(); + cf.format(std::back_inserter(s), args...); + return s; } template ::value)> -OutputIt format_to(OutputIt out, const CompiledFormat& cf, - const Args&... args) { +constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, + const Args&... args) { return cf.format(out, args...); } -# endif // __cpp_if_constexpr -#endif // FMT_USE_CONSTEXPR - -template ::value)> -std::basic_string format(const CompiledFormat& cf, const Args&... args) { - basic_memory_buffer buffer; - using context = buffer_context; - detail::buffer& base = buffer; - detail::cf::vformat_to(std::back_inserter(base), cf, - make_format_args(args...)); - return to_string(buffer); -} template ::value)> FMT_INLINE std::basic_string format(const S&, Args&&... args) { - constexpr basic_string_view str = S(); - if (str.size() == 2 && str[0] == '{' && str[1] == '}') - return fmt::to_string(detail::first(args...)); + if constexpr (std::is_same::value) { + constexpr auto str = basic_string_view(S()); + if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { + const auto& first = detail::first(args...); + if constexpr (detail::is_named_arg< + remove_cvref_t>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } constexpr auto compiled = detail::compile(S()); - return format(compiled, std::forward(args)...); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format( + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format(compiled, std::forward(args)...); + } } -template ::value)> -OutputIt format_to(OutputIt out, const CompiledFormat& cf, - const Args&... args) { - using char_type = typename CompiledFormat::char_type; - using context = format_context_t; - return detail::cf::vformat_to(out, cf, - make_format_args(args...)); +template ::value)> +FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_to( + out, static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_to(out, compiled, std::forward(args)...); + } } +#endif template ::value)> -OutputIt format_to(OutputIt out, const S&, const Args&... args) { - constexpr auto compiled = detail::compile(S()); - return format_to(out, compiled, args...); +auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); + return {buf.out(), buf.count()}; } -template < - typename OutputIt, typename CompiledFormat, typename... Args, - FMT_ENABLE_IF(detail::is_output_iterator::value&& std::is_base_of< - detail::basic_compiled_format, CompiledFormat>::value)> -format_to_n_result format_to_n(OutputIt out, size_t n, - const CompiledFormat& cf, - const Args&... args) { - auto it = - format_to(detail::truncating_iterator(out, n), cf, args...); - return {it.base(), it.count()}; +template ::value)> +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) + -> size_t { + return fmt::format_to(detail::counting_iterator(), fmt, args...).count(); } -template -size_t formatted_size(const CompiledFormat& cf, const Args&... args) { - return format_to(detail::counting_iterator(), cf, args...).count(); +template ::value)> +void print(std::FILE* f, const S& fmt, const Args&... args) { + memory_buffer buffer; + fmt::format_to(std::back_inserter(buffer), fmt, args...); + detail::print(f, {buffer.data(), buffer.size()}); } +template ::value)> +void print(const S& fmt, const Args&... args) { + print(stdout, fmt, args...); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +template constexpr auto operator""_cf() { + using char_t = remove_cvref_t; + return detail::udl_compiled_string(); +} +} // namespace literals +#endif + +FMT_END_EXPORT FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/external/fmt/include/fmt/core.h b/external/fmt/include/fmt/core.h index 3c9fea1aca..8ca735f0c0 100644 --- a/external/fmt/include/fmt/core.h +++ b/external/fmt/include/fmt/core.h @@ -1,1887 +1,5 @@ -// Formatting library for C++ - the core API -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. +// This file is only provided for compatibility and may be removed in future +// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h +// otherwise. -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include -#include // std::FILE -#include -#include -#include -#include -#include -#include -#include - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 70003 - -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#if defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#else -# define FMT_ICC_VERSION 0 -#endif - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION -#else -# define FMT_HAS_GXX_CXX11 0 -#endif - -#ifdef __NVCC__ -# define FMT_NVCC __NVCC__ -#else -# define FMT_NVCC 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) -#else -# define FMT_MSC_VER 0 -# define FMT_SUPPRESS_MSC_WARNING(n) -#endif -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ - !(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ - (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ - !FMT_NVCC && !FMT_ICC_VERSION -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -# define FMT_CONSTEXPR_DECL constexpr -#else -# define FMT_CONSTEXPR inline -# define FMT_CONSTEXPR_DECL -#endif - -#ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_override) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 || \ - FMT_CLANG_VERSION > 600 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif -#endif - -// Check if exceptions are disabled. -#ifndef FMT_EXCEPTIONS -# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ - FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -# else -# define FMT_EXCEPTIONS 1 -# endif -#endif - -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -# define FMT_HAS_CXX11_NOEXCEPT 1 -#else -# define FMT_DETECTED_NOEXCEPT throw() -# define FMT_HAS_CXX11_NOEXCEPT 0 -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT -# else -# define FMT_NOEXCEPT -# endif -#endif - -// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code -// warnings. -#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \ - !FMT_NVCC -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if defined(__GNUC__) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VER -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - -// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC -# define FMT_DEPRECATED_ALIAS -#else -# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED -#endif - -#ifndef FMT_INLINE -# if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_INLINE inline __attribute__((always_inline)) -# else -# define FMT_INLINE inline -# endif -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - FMT_MSC_VER >= 1900 -# define FMT_INLINE_NAMESPACE inline namespace -# define FMT_END_NAMESPACE \ - } \ - } -# else -# define FMT_INLINE_NAMESPACE namespace -# define FMT_END_NAMESPACE \ - } \ - using namespace v7; \ - } -# endif -# define FMT_BEGIN_NAMESPACE \ - namespace fmt { \ - FMT_INLINE_NAMESPACE v7 { -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# define FMT_EXTERN_TEMPLATE_API FMT_API -# define FMT_EXPORTED -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# define FMT_EXTERN_TEMPLATE_API FMT_API -# endif -#else -# define FMT_CLASS_API -#endif -#ifndef FMT_API -# define FMT_API -#endif -#ifndef FMT_EXTERN_TEMPLATE_API -# define FMT_EXTERN_TEMPLATE_API -#endif -#ifndef FMT_INSTANTIATION_DEF_API -# define FMT_INSTANTIATION_DEF_API FMT_API -#endif - -#ifndef FMT_HEADER_ONLY -# define FMT_EXTERN extern -#else -# define FMT_EXTERN -#endif - -// libc++ supports string_view in pre-c++17. -#if (FMT_HAS_INCLUDE() && \ - (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) -# include -# define FMT_USE_STRING_VIEW -#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L -# include -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -#ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VER -#endif -#if FMT_UNICODE && FMT_MSC_VER -# pragma execution_character_set("utf-8") -#endif - -FMT_BEGIN_NAMESPACE - -// Implementations of enable_if_t and other metafunctions for older systems. -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; -template using bool_constant = std::integral_constant; -template -using remove_reference_t = typename std::remove_reference::type; -template -using remove_const_t = typename std::remove_const::type; -template -using remove_cvref_t = typename std::remove_cv>::type; -template struct type_identity { using type = T; }; -template using type_identity_t = typename type_identity::type; - -struct monostate {}; - -// An enable_if helper to be used in template parameters which results in much -// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed -// to workaround a bug in MSVC 2019 (see #1140 and #1186). -#define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 - -namespace detail { - -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -template constexpr T const_check(T value) { return value; } - -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, - const char* message); - -#ifndef FMT_ASSERT -# ifdef NDEBUG -// FMT_ASSERT is not empty to avoid -Werror=empty-body. -# define FMT_ASSERT(condition, message) ((void)0) -# else -# define FMT_ASSERT(condition, message) \ - ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ - ? (void)0 \ - : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) -# endif -#endif - -#if defined(FMT_USE_STRING_VIEW) -template using std_string_view = std::basic_string_view; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) -template -using std_string_view = std::experimental::basic_string_view; -#else -template struct std_string_view {}; -#endif - -#ifdef FMT_USE_INT128 -// Do nothing. -#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && !(FMT_CLANG_VERSION && FMT_MSC_VER) -# define FMT_USE_INT128 1 -using int128_t = __int128_t; -using uint128_t = __uint128_t; -#else -# define FMT_USE_INT128 0 -#endif -#if !FMT_USE_INT128 -struct int128_t {}; -struct uint128_t {}; -#endif - -// Casts a nonnegative integer to unsigned. -template -FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast::type>(value); -} - -FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; - -template constexpr bool is_unicode() { - return FMT_UNICODE || sizeof(Char) != 1 || - (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); -} - -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif -} // namespace detail - -#ifdef FMT_USE_INTERNAL -namespace internal = detail; // DEPRECATED -#endif - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -template class basic_string_view { - private: - const Char* data_; - size_t size_; - - public: - using value_type = Char; - using iterator = const Char*; - - constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT - : data_(s), - size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits::length``. - \endrst - */ -#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr. - FMT_CONSTEXPR -#endif - basic_string_view(const Char* s) - : data_(s), size_(std::char_traits::length(s)) {} - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template - FMT_CONSTEXPR basic_string_view( - const std::basic_string& s) FMT_NOEXCEPT - : data_(s.data()), - size_(s.size()) {} - - template >::value)> - FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), - size_(s.size()) {} - - /** Returns a pointer to the string data. */ - constexpr const Char* data() const { return data_; } - - /** Returns the string size. */ - constexpr size_t size() const { return size_; } - - constexpr iterator begin() const { return data_; } - constexpr iterator end() const { return data_ + size_; } - - constexpr const Char& operator[](size_t pos) const { return data_[pos]; } - - FMT_CONSTEXPR void remove_prefix(size_t n) { - data_ += n; - size_ -= n; - } - - // Lexicographically compare this string reference to other. - int compare(basic_string_view other) const { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - friend bool operator==(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) == 0; - } - friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) != 0; - } - friend bool operator<(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) < 0; - } - friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) <= 0; - } - friend bool operator>(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) > 0; - } - friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) >= 0; - } -}; - -using string_view = basic_string_view; -using wstring_view = basic_string_view; - -/** Specifies if ``T`` is a character type. Can be specialized by users. */ -template struct is_char : std::false_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; - -/** - \rst - Returns a string view of `s`. In order to add custom string type support to - {fmt} provide an overload of `to_string_view` for it in the same namespace as - the type for the argument-dependent lookup to work. - - **Example**:: - - namespace my_ns { - inline string_view to_string_view(const my_string& s) { - return {s.data(), s.length()}; - } - } - std::string message = fmt::format(my_string("The answer is {}"), 42); - \endrst - */ -template ::value)> -inline basic_string_view to_string_view(const Char* s) { - return s; -} - -template -inline basic_string_view to_string_view( - const std::basic_string& s) { - return s; -} - -template -inline basic_string_view to_string_view(basic_string_view s) { - return s; -} - -template >::value)> -inline basic_string_view to_string_view(detail::std_string_view s) { - return s; -} - -// A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). -struct compile_string {}; - -template -struct is_compile_string : std::is_base_of {}; - -template ::value)> -constexpr basic_string_view to_string_view(const S& s) { - return s; -} - -namespace detail { -void to_string_view(...); -using fmt::v7::to_string_view; - -// Specifies whether S is a string type convertible to fmt::basic_string_view. -// It should be a constexpr function but MSVC 2017 fails to compile it in -// enable_if and MSVC 2015 fails to compile it as an alias template. -template -struct is_string : std::is_class()))> { -}; - -template struct char_t_impl {}; -template struct char_t_impl::value>> { - using result = decltype(to_string_view(std::declval())); - using type = typename result::value_type; -}; - -struct error_handler { - constexpr error_handler() = default; - constexpr error_handler(const error_handler&) = default; - error_handler& operator=(const error_handler&) = default; - - // This function is intentionally not constexpr to give a compile-time error. - FMT_NORETURN FMT_API void on_error(const char* message); -}; -} // namespace detail - -/** String's character type. */ -template using char_t = typename detail::char_t_impl::type; - -/** - \rst - Parsing context consisting of a format string range being parsed and an - argument counter for automatic indexing. - - You can use one of the following type aliases for common character types: - - +-----------------------+-------------------------------------+ - | Type | Definition | - +=======================+=====================================+ - | format_parse_context | basic_format_parse_context | - +-----------------------+-------------------------------------+ - | wformat_parse_context | basic_format_parse_context | - +-----------------------+-------------------------------------+ - \endrst - */ -template -class basic_format_parse_context : private ErrorHandler { - private: - basic_string_view format_str_; - int next_arg_id_; - - public: - using char_type = Char; - using iterator = typename basic_string_view::iterator; - - explicit constexpr basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = {}) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} - - /** - Returns an iterator to the beginning of the format string range being - parsed. - */ - constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } - - /** - Returns an iterator past the end of the format string range being parsed. - */ - constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } - - /** Advances the begin iterator to ``it``. */ - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(detail::to_unsigned(it - begin())); - } - - /** - Reports an error if using the manual argument indexing; otherwise returns - the next argument index and switches to the automatic indexing. - */ - FMT_CONSTEXPR int next_arg_id() { - // Don't check if the argument id is valid to avoid overhead and because it - // will be checked during formatting anyway. - if (next_arg_id_ >= 0) return next_arg_id_++; - on_error("cannot switch from manual to automatic argument indexing"); - return 0; - } - - /** - Reports an error if using the automatic argument indexing; otherwise - switches to the manual indexing. - */ - FMT_CONSTEXPR void check_arg_id(int) { - if (next_arg_id_ > 0) - on_error("cannot switch from automatic to manual argument indexing"); - else - next_arg_id_ = -1; - } - - FMT_CONSTEXPR void check_arg_id(basic_string_view) {} - - FMT_CONSTEXPR void on_error(const char* message) { - ErrorHandler::on_error(message); - } - - constexpr ErrorHandler error_handler() const { return *this; } -}; - -using format_parse_context = basic_format_parse_context; -using wformat_parse_context = basic_format_parse_context; - -template class basic_format_arg; -template class basic_format_args; -template class dynamic_format_arg_store; - -// A formatter for objects of type T. -template -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; -}; - -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template -using has_formatter = - std::is_constructible>; - -namespace detail { - -/** - \rst - A contiguous memory buffer with an optional growing ability. It is an internal - class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. - \endrst - */ -template class buffer { - private: - T* ptr_; - size_t size_; - size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_SUPPRESS_MSC_WARNING(26495) - buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} - - buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT - : ptr_(p), - size_(sz), - capacity_(cap) {} - - /** Sets the buffer data and capacity. */ - void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(size_t capacity) = 0; - - public: - using value_type = T; - using const_reference = const T&; - - buffer(const buffer&) = delete; - void operator=(const buffer&) = delete; - virtual ~buffer() = default; - - T* begin() FMT_NOEXCEPT { return ptr_; } - T* end() FMT_NOEXCEPT { return ptr_ + size_; } - - const T* begin() const FMT_NOEXCEPT { return ptr_; } - const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - size_t size() const FMT_NOEXCEPT { return size_; } - - /** Returns the capacity of this buffer. */ - size_t capacity() const FMT_NOEXCEPT { return capacity_; } - - /** Returns a pointer to the buffer data. */ - T* data() FMT_NOEXCEPT { return ptr_; } - - /** Returns a pointer to the buffer data. */ - const T* data() const FMT_NOEXCEPT { return ptr_; } - - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. - */ - void resize(size_t new_size) { - reserve(new_size); - size_ = new_size; - } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - /** Reserves space to store at least *capacity* elements. */ - void reserve(size_t new_capacity) { - if (new_capacity > capacity_) grow(new_capacity); - } - - void push_back(const T& value) { - reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template void append(const U* begin, const U* end); - - template T& operator[](I index) { return ptr_[index]; } - template const T& operator[](I index) const { - return ptr_[index]; - } -}; - -// A container-backed buffer. -template -class container_buffer : public buffer { - private: - Container& container_; - - protected: - void grow(size_t capacity) FMT_OVERRIDE { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit container_buffer(Container& c_) - : buffer(c_.size()), container_(c_) {} -}; - -// Extracts a reference to the container from back_insert_iterator. -template -inline Container& get_container(std::back_insert_iterator it) { - using bi_iterator = std::back_insert_iterator; - struct accessor : bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; - }; - return *accessor(it).container; -} - -template -struct fallback_formatter { - fallback_formatter() = delete; -}; - -// Specifies if T has an enabled fallback_formatter specialization. -template -using has_fallback_formatter = - std::is_constructible>; - -struct view {}; - -template struct named_arg : view { - const Char* name; - const T& value; - named_arg(const Char* n, const T& v) : name(n), value(v) {} -}; - -template struct named_arg_info { - const Char* name; - int id; -}; - -template -struct arg_data { - // args_[0].named_args points to named_args_ to avoid bloating format_args. - T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)]; - named_arg_info named_args_[NUM_NAMED_ARGS]; - - template - arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} - arg_data(const arg_data& other) = delete; - const T* args() const { return args_ + 1; } - named_arg_info* named_args() { return named_args_; } -}; - -template -struct arg_data { - T args_[NUM_ARGS != 0 ? NUM_ARGS : 1]; - - template - FMT_INLINE arg_data(const U&... init) : args_{init...} {} - FMT_INLINE const T* args() const { return args_; } - FMT_INLINE std::nullptr_t named_args() { return nullptr; } -}; - -template -inline void init_named_args(named_arg_info*, int, int) {} - -template -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T&, const Tail&... args) { - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const named_arg& arg, - const Tail&... args) { - named_args[named_arg_count++] = {arg.name, arg_count}; - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template -FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} - -template struct is_named_arg : std::false_type {}; - -template -struct is_named_arg> : std::true_type {}; - -template constexpr size_t count() { return B ? 1 : 0; } -template constexpr size_t count() { - return (B1 ? 1 : 0) + count(); -} - -template constexpr size_t count_named_args() { - return count::value...>(); -} - -enum class type { - none_type, - // Integer types should go first, - int_type, - uint_type, - long_long_type, - ulong_long_type, - int128_type, - uint128_type, - bool_type, - char_type, - last_integer_type = char_type, - // followed by floating-point types. - float_type, - double_type, - long_double_type, - last_numeric_type = long_double_type, - cstring_type, - string_type, - pointer_type, - custom_type -}; - -// Maps core type T to the corresponding type enum constant. -template -struct type_constant : std::integral_constant {}; - -#define FMT_TYPE_CONSTANT(Type, constant) \ - template \ - struct type_constant \ - : std::integral_constant {} - -FMT_TYPE_CONSTANT(int, int_type); -FMT_TYPE_CONSTANT(unsigned, uint_type); -FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_t, int128_type); -FMT_TYPE_CONSTANT(uint128_t, uint128_type); -FMT_TYPE_CONSTANT(bool, bool_type); -FMT_TYPE_CONSTANT(Char, char_type); -FMT_TYPE_CONSTANT(float, float_type); -FMT_TYPE_CONSTANT(double, double_type); -FMT_TYPE_CONSTANT(long double, long_double_type); -FMT_TYPE_CONSTANT(const Char*, cstring_type); -FMT_TYPE_CONSTANT(basic_string_view, string_type); -FMT_TYPE_CONSTANT(const void*, pointer_type); - -constexpr bool is_integral_type(type t) { - return t > type::none_type && t <= type::last_integer_type; -} - -constexpr bool is_arithmetic_type(type t) { - return t > type::none_type && t <= type::last_numeric_type; -} - -template struct string_value { - const Char* data; - size_t size; -}; - -template struct named_arg_value { - const named_arg_info* data; - size_t size; -}; - -template struct custom_value { - using parse_context = typename Context::parse_context_type; - const void* value; - void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); -}; - -// A formatting argument value. -template class value { - public: - using char_type = typename Context::char_type; - - union { - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - int128_t int128_value; - uint128_t uint128_value; - bool bool_value; - char_type char_value; - float float_value; - double double_value; - long double long_double_value; - const void* pointer; - string_value string; - custom_value custom; - named_arg_value named_args; - }; - - constexpr FMT_INLINE value(int val = 0) : int_value(val) {} - constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} - FMT_INLINE value(long long val) : long_long_value(val) {} - FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} - FMT_INLINE value(int128_t val) : int128_value(val) {} - FMT_INLINE value(uint128_t val) : uint128_value(val) {} - FMT_INLINE value(float val) : float_value(val) {} - FMT_INLINE value(double val) : double_value(val) {} - FMT_INLINE value(long double val) : long_double_value(val) {} - FMT_INLINE value(bool val) : bool_value(val) {} - FMT_INLINE value(char_type val) : char_value(val) {} - FMT_INLINE value(const char_type* val) { string.data = val; } - FMT_INLINE value(basic_string_view val) { - string.data = val.data(); - string.size = val.size(); - } - FMT_INLINE value(const void* val) : pointer(val) {} - FMT_INLINE value(const named_arg_info* args, size_t size) - : named_args{args, size} {} - - template FMT_INLINE value(const T& val) { - custom.value = &val; - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter` for `format` and - // `printf_formatter` for `printf`. - custom.format = format_custom_arg< - T, conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; - } - - private: - // Formats an argument of a custom type, such as a user-defined class. - template - static void format_custom_arg(const void* arg, - typename Context::parse_context_type& parse_ctx, - Context& ctx) { - Formatter f; - parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast(arg), ctx)); - } -}; - -template -FMT_CONSTEXPR basic_format_arg make_arg(const T& value); - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -enum { long_short = sizeof(long) == sizeof(int) }; -using long_type = conditional_t; -using ulong_type = conditional_t; - -// Maps formatting arguments to core types. -template struct arg_mapper { - using char_type = typename Context::char_type; - - FMT_CONSTEXPR int map(signed char val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned char val) { return val; } - FMT_CONSTEXPR int map(short val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned short val) { return val; } - FMT_CONSTEXPR int map(int val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned val) { return val; } - FMT_CONSTEXPR long_type map(long val) { return val; } - FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; } - FMT_CONSTEXPR long long map(long long val) { return val; } - FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; } - FMT_CONSTEXPR int128_t map(int128_t val) { return val; } - FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; } - FMT_CONSTEXPR bool map(bool val) { return val; } - - template ::value)> - FMT_CONSTEXPR char_type map(T val) { - static_assert( - std::is_same::value || std::is_same::value, - "mixing character types is disallowed"); - return val; - } - - FMT_CONSTEXPR float map(float val) { return val; } - FMT_CONSTEXPR double map(double val) { return val; } - FMT_CONSTEXPR long double map(long double val) { return val; } - - FMT_CONSTEXPR const char_type* map(char_type* val) { return val; } - FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; } - template ::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { - static_assert(std::is_same>::value, - "mixing character types is disallowed"); - return to_string_view(val); - } - template , T>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { - return basic_string_view(val); - } - template < - typename T, - FMT_ENABLE_IF( - std::is_constructible, T>::value && - !std::is_constructible, T>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR basic_string_view map(const T& val) { - return std_string_view(val); - } - FMT_CONSTEXPR const char* map(const signed char* val) { - static_assert(std::is_same::value, "invalid string type"); - return reinterpret_cast(val); - } - FMT_CONSTEXPR const char* map(const unsigned char* val) { - static_assert(std::is_same::value, "invalid string type"); - return reinterpret_cast(val); - } - FMT_CONSTEXPR const char* map(signed char* val) { - const auto* const_val = val; - return map(const_val); - } - FMT_CONSTEXPR const char* map(unsigned char* val) { - const auto* const_val = val; - return map(const_val); - } - - FMT_CONSTEXPR const void* map(void* val) { return val; } - FMT_CONSTEXPR const void* map(const void* val) { return val; } - FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; } - template FMT_CONSTEXPR int map(const T*) { - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); - return 0; - } - - template ::value && - !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR auto map(const T& val) - -> decltype(std::declval().map( - static_cast::type>(val))) { - return map(static_cast::type>(val)); - } - template ::value && !is_char::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR const T& map(const T& val) { - return val; - } - - template - FMT_CONSTEXPR auto map(const named_arg& val) - -> decltype(std::declval().map(val.value)) { - return map(val.value); - } - - int map(...) { - constexpr bool formattable = sizeof(Context) == 0; - static_assert( - formattable, - "Cannot format argument. To make type T formattable provide a " - "formatter specialization: " - "https://fmt.dev/latest/api.html#formatting-user-defined-types"); - return 0; - } -}; - -// A type constant after applying arg_mapper. -template -using mapped_type_constant = - type_constant().map(std::declval())), - typename Context::char_type>; - -enum { packed_arg_bits = 4 }; -// Maximum number of arguments with packed types. -enum { max_packed_args = 62 / packed_arg_bits }; -enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; -enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -} // namespace detail - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template class basic_format_arg { - private: - detail::value value_; - detail::type type_; - - template - friend FMT_CONSTEXPR basic_format_arg detail::make_arg( - const T& value); - - template - friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)); - - friend class basic_format_args; - friend class dynamic_format_arg_store; - - using char_type = typename Context::char_type; - - template - friend struct detail::arg_data; - - basic_format_arg(const detail::named_arg_info* args, size_t size) - : value_(args, size) {} - - public: - class handle { - public: - explicit handle(detail::custom_value custom) : custom_(custom) {} - - void format(typename Context::parse_context_type& parse_ctx, - Context& ctx) const { - custom_.format(custom_.value, parse_ctx, ctx); - } - - private: - detail::custom_value custom_; - }; - - constexpr basic_format_arg() : type_(detail::type::none_type) {} - - constexpr explicit operator bool() const FMT_NOEXCEPT { - return type_ != detail::type::none_type; - } - - detail::type type() const { return type_; } - - bool is_integral() const { return detail::is_integral_type(type_); } - bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } -}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -template -FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( - Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { - using char_type = typename Context::char_type; - switch (arg.type_) { - case detail::type::none_type: - break; - case detail::type::int_type: - return vis(arg.value_.int_value); - case detail::type::uint_type: - return vis(arg.value_.uint_value); - case detail::type::long_long_type: - return vis(arg.value_.long_long_value); - case detail::type::ulong_long_type: - return vis(arg.value_.ulong_long_value); -#if FMT_USE_INT128 - case detail::type::int128_type: - return vis(arg.value_.int128_value); - case detail::type::uint128_type: - return vis(arg.value_.uint128_value); -#else - case detail::type::int128_type: - case detail::type::uint128_type: - break; -#endif - case detail::type::bool_type: - return vis(arg.value_.bool_value); - case detail::type::char_type: - return vis(arg.value_.char_value); - case detail::type::float_type: - return vis(arg.value_.float_value); - case detail::type::double_type: - return vis(arg.value_.double_value); - case detail::type::long_double_type: - return vis(arg.value_.long_double_value); - case detail::type::cstring_type: - return vis(arg.value_.string.data); - case detail::type::string_type: - return vis(basic_string_view(arg.value_.string.data, - arg.value_.string.size)); - case detail::type::pointer_type: - return vis(arg.value_.pointer); - case detail::type::custom_type: - return vis(typename basic_format_arg::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; -template -struct is_contiguous> : std::true_type {}; - -namespace detail { - -template -struct is_back_insert_iterator : std::false_type {}; -template -struct is_back_insert_iterator> - : std::true_type {}; - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; - -// A type-erased reference to an std::locale to avoid heavy include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - - explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } - - template Locale get() const; -}; - -template constexpr unsigned long long encode_types() { return 0; } - -template -constexpr unsigned long long encode_types() { - return static_cast(mapped_type_constant::value) | - (encode_types() << packed_arg_bits); -} - -template -FMT_CONSTEXPR basic_format_arg make_arg(const T& value) { - basic_format_arg arg; - arg.type_ = mapped_type_constant::value; - arg.value_ = arg_mapper().map(value); - return arg; -} - -// The type template parameter is there to avoid an ODR violation when using -// a fallback formatter in one translation unit and an implicit conversion in -// another (not recommended). -template -inline value make_arg(const T& val) { - return arg_mapper().map(val); -} - -template -inline basic_format_arg make_arg(const T& value) { - return make_arg(value); -} - -template struct is_reference_wrapper : std::false_type {}; -template -struct is_reference_wrapper> : std::true_type {}; - -template const T& unwrap(const T& v) { return v; } -template const T& unwrap(const std::reference_wrapper& v) { - return static_cast(v); -} - -class dynamic_arg_list { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - template struct node { - virtual ~node() = default; - std::unique_ptr> next; - }; - - template struct typed_node : node<> { - T value; - - template - FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} - - template - FMT_CONSTEXPR typed_node(const basic_string_view& arg) - : value(arg.data(), arg.size()) {} - }; - - std::unique_ptr> head_; - - public: - template const T& push(const Arg& arg) { - auto new_node = std::unique_ptr>(new typed_node(arg)); - auto& value = new_node->value; - new_node->next = std::move(head_); - head_ = std::move(new_node); - return value; - } -}; -} // namespace detail - -// Formatting context. -template class basic_format_context { - public: - /** The character type for the output. */ - using char_type = Char; - - private: - OutputIt out_; - basic_format_args args_; - detail::locale_ref loc_; - - public: - using iterator = OutputIt; - using format_arg = basic_format_arg; - using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - - basic_format_context(const basic_format_context&) = delete; - void operator=(const basic_format_context&) = delete; - /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. - */ - basic_format_context(OutputIt out, - basic_format_args ctx_args, - detail::locale_ref loc = detail::locale_ref()) - : out_(out), args_(ctx_args), loc_(loc) {} - - format_arg arg(int id) const { return args_.get(id); } - format_arg arg(basic_string_view name) { return args_.get(name); } - int arg_id(basic_string_view name) { return args_.get_id(name); } - const basic_format_args& args() const { return args_; } - - detail::error_handler error_handler() { return {}; } - void on_error(const char* message) { error_handler().on_error(message); } - - // Returns an iterator to the beginning of the output range. - iterator out() { return out_; } - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } - - detail::locale_ref locale() { return loc_; } -}; - -template -using buffer_context = - basic_format_context>, Char>; -using format_context = buffer_context; -using wformat_context = buffer_context; - -// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164. -#define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context>, Char> - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template -class format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - static const size_t num_args = sizeof...(Args); - static const size_t num_named_args = detail::count_named_args(); - static const bool is_packed = num_args <= detail::max_packed_args; - - using value_type = conditional_t, - basic_format_arg>; - - detail::arg_data - data_; - - friend class basic_format_args; - - static constexpr unsigned long long desc = - (is_packed ? detail::encode_types() - : detail::is_unpacked_bit | num_args) | - (num_named_args != 0 - ? static_cast(detail::has_named_args_bit) - : 0); - - public: - format_arg_store(const Args&... args) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(*this), -#endif - data_{detail::make_arg< - is_packed, Context, - detail::mapped_type_constant::value>(args)...} { - detail::init_named_args(data_.named_args(), 0, 0, args...); - } -}; - -/** - \rst - Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. - See `~fmt::arg` for lifetime considerations. - \endrst - */ -template -inline format_arg_store make_format_args( - const Args&... args) { - return {args...}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template -inline detail::named_arg arg(const Char* name, const T& arg) { - static_assert(!detail::is_named_arg(), "nested named arguments"); - return {name, arg}; -} - -/** - \rst - A dynamic version of `fmt::format_arg_store`. - It's equipped with a storage to potentially temporary objects which lifetimes - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_copy { - static constexpr detail::type mapped_type = - detail::mapped_type_constant::value; - - enum { - value = !(detail::is_reference_wrapper::value || - std::is_same>::value || - std::is_same>::value || - (mapped_type != detail::type::cstring_type && - mapped_type != detail::type::string_type && - mapped_type != detail::type::custom_type)) - }; - }; - - template - using stored_type = conditional_t::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous. - std::vector> data_; - std::vector> named_info_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - detail::dynamic_arg_list dynamic_args_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return detail::is_unpacked_bit | data_.size() | - (named_info_.empty() - ? 0ULL - : static_cast(detail::has_named_args_bit)); - } - - const basic_format_arg* data() const { - return named_info_.empty() ? data_.data() : data_.data() + 1; - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(detail::make_arg(arg)); - } - - template - void emplace_arg(const detail::named_arg& arg) { - if (named_info_.empty()) { - constexpr const detail::named_arg_info* zero_ptr{nullptr}; - data_.insert(data_.begin(), {zero_ptr, 0}); - } - data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); - auto pop_one = [](std::vector>* data) { - data->pop_back(); - }; - std::unique_ptr>, decltype(pop_one)> - guard{&data_, pop_one}; - named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); - data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; - guard.release(); - } - - public: - /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template - void push_back(const T& arg) { - if (detail::const_check(need_copy::value)) { - emplace_arg(dynamic_args_.push >(arg)); - } else { - emplace_arg(detail::unwrap(arg)); - } - } - - /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. Supports named arguments wrapped in - ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``. - - **Example**:: - - fmt::dynamic_format_arg_store store; - char str[] = "1234567890"; - store.push_back(std::cref(str)); - int a1_val{42}; - auto a1 = fmt::arg("a1_", a1_val); - store.push_back(std::cref(a1)); - - // Changing str affects the output but only for string and custom types. - str[0] = 'X'; - - std::string result = fmt::vformat("{} and {a1_}"); - assert(result == "X234567890 and 42"); - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - detail::is_named_arg::type>::value || - need_copy::value, - "objects of built-in types and string views are always copied"); - emplace_arg(arg.get()); - } - - /** - Adds named argument into the dynamic store for later passing to a formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. - */ - template - void push_back(const detail::named_arg& arg) { - const char_type* arg_name = - dynamic_args_.push>(arg.name).c_str(); - if (detail::const_check(need_copy::value)) { - emplace_arg( - fmt::arg(arg_name, dynamic_args_.push>(arg.value))); - } else { - emplace_arg(fmt::arg(arg_name, arg.value)); - } - } - - /** Erase all elements from the store */ - void clear() { - data_.clear(); - named_info_.clear(); - dynamic_args_ = detail::dynamic_arg_list(); - } - - /** - \rst - Reserves space to store at least *new_cap* arguments including - *new_cap_named* named arguments. - \endrst - */ - void reserve(size_t new_cap, size_t new_cap_named) { - FMT_ASSERT(new_cap >= new_cap_named, - "Set of arguments includes set of named arguments"); - data_.reserve(new_cap); - named_info_.reserve(new_cap_named); - } -}; - -/** - \rst - A view of a collection of formatting arguments. To avoid lifetime issues it - should only be used as a parameter type in type-erased functions such as - ``vformat``:: - - void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(42); // Error: dangling reference - \endrst - */ -template class basic_format_args { - public: - using size_type = int; - using format_arg = basic_format_arg; - - private: - // A descriptor that contains information about formatting arguments. - // If the number of arguments is less or equal to max_packed_args then - // argument types are passed in the descriptor. This reduces binary code size - // per formatting function call. - unsigned long long desc_; - union { - // If is_packed() returns true then argument values are stored in values_; - // otherwise they are stored in args_. This is done to improve cache - // locality and reduce compiled code size since storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const detail::value* values_; - const format_arg* args_; - }; - - bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } - bool has_named_args() const { - return (desc_ & detail::has_named_args_bit) != 0; - } - - detail::type type(int index) const { - int shift = index * detail::packed_arg_bits; - unsigned int mask = (1 << detail::packed_arg_bits) - 1; - return static_cast((desc_ >> shift) & mask); - } - - basic_format_args(unsigned long long desc, - const detail::value* values) - : desc_(desc), values_(values) {} - basic_format_args(unsigned long long desc, const format_arg* args) - : desc_(desc), args_(args) {} - - public: - basic_format_args() : desc_(0) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template - FMT_INLINE basic_format_args(const format_arg_store& store) - : basic_format_args(store.desc, store.data_.args()) {} - - /** - \rst - Constructs a `basic_format_args` object from - `~fmt::dynamic_format_arg_store`. - \endrst - */ - FMT_INLINE basic_format_args(const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data()) {} - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - basic_format_args(const format_arg* args, int count) - : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), - args) {} - - /** Returns the argument with the specified id. */ - format_arg get(int id) const { - format_arg arg; - if (!is_packed()) { - if (id < max_size()) arg = args_[id]; - return arg; - } - if (id >= detail::max_packed_args) return arg; - arg.type_ = type(id); - if (arg.type_ == detail::type::none_type) return arg; - arg.value_ = values_[id]; - return arg; - } - - template format_arg get(basic_string_view name) const { - int id = get_id(name); - return id >= 0 ? get(id) : format_arg(); - } - - template int get_id(basic_string_view name) const { - if (!has_named_args()) return -1; - const auto& named_args = - (is_packed() ? values_[-1] : args_[-1].value_).named_args; - for (size_t i = 0; i < named_args.size; ++i) { - if (named_args.data[i].name == name) return named_args.data[i].id; - } - return -1; - } - - int max_size() const { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); - } -}; - -/** An alias to ``basic_format_args``. */ -// It is a separate type rather than an alias to make symbols readable. -struct format_args : basic_format_args { - template - FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {} -}; -struct wformat_args : basic_format_args { - using basic_format_args::basic_format_args; -}; - -namespace detail { - -// Reports a compile-time error if S is not a valid format string. -template ::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif -} -template ::value)> -void check_format_string(S); - -template > -inline format_arg_store, remove_reference_t...> -make_args_checked(const S& format_str, - const remove_reference_t&... args) { - static_assert(count<(std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); - check_format_string(format_str); - return {args...}; -} - -template ::value)> -std::basic_string vformat( - basic_string_view format_str, - basic_format_args>> args); - -FMT_API std::string vformat(string_view format_str, format_args args); - -template -typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to( - buffer& buf, basic_string_view format_str, - basic_format_args)> args); - -template ::value)> -inline void vprint_mojibake(std::FILE*, basic_string_view, const Args&) {} - -FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); -#ifndef _WIN32 -inline void vprint_mojibake(std::FILE*, string_view, format_args) {} -#endif -} // namespace detail - -/** Formats a string and writes the output to ``out``. */ -// GCC 8 and earlier cannot handle std::back_insert_iterator with -// vformat_to(...) overload, so SFINAE on iterator type instead. -template < - typename OutputIt, typename S, typename Char = char_t, - FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator::value)> -OutputIt vformat_to( - OutputIt out, const S& format_str, - basic_format_args>> args) { - auto& c = detail::get_container(out); - detail::container_buffer> buf(c); - detail::vformat_to(buf, to_string_view(format_str), args); - return out; -} - -template ::value&& detail::is_string::value)> -inline std::back_insert_iterator format_to( - std::back_insert_iterator out, const S& format_str, - Args&&... args) { - return vformat_to(out, to_string_view(format_str), - detail::make_args_checked(format_str, args...)); -} - -template > -FMT_INLINE std::basic_string vformat( - const S& format_str, - basic_format_args>> args) { - return detail::vformat(to_string_view(format_str), args); -} - -/** - \rst - Formats arguments and returns the result as a string. - - **Example**:: - - #include - std::string message = fmt::format("The answer is {}", 42); - \endrst -*/ -// Pass char_t as a default template parameter instead of using -// std::basic_string> to reduce the symbol size. -template > -FMT_INLINE std::basic_string format(const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::vformat(to_string_view(format_str), vargs); -} - -FMT_API void vprint(string_view, format_args); -FMT_API void vprint(std::FILE*, string_view, format_args); - -/** - \rst - Formats ``args`` according to specifications in ``format_str`` and writes the - output to the file ``f``. Strings are assumed to be Unicode-encoded unless the - ``FMT_UNICODE`` macro is set to 0. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template > -inline void print(std::FILE* f, const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::is_unicode() - ? vprint(f, to_string_view(format_str), vargs) - : detail::vprint_mojibake(f, to_string_view(format_str), vargs); -} - -/** - \rst - Formats ``args`` according to specifications in ``format_str`` and writes - the output to ``stdout``. Strings are assumed to be Unicode-encoded unless - the ``FMT_UNICODE`` macro is set to 0. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template > -inline void print(const S& format_str, Args&&... args) { - const auto& vargs = detail::make_args_checked(format_str, args...); - return detail::is_unicode() - ? vprint(to_string_view(format_str), vargs) - : detail::vprint_mojibake(stdout, to_string_view(format_str), - vargs); -} -FMT_END_NAMESPACE - -#endif // FMT_CORE_H_ +#include "format.h" diff --git a/external/fmt/include/fmt/format-inl.h b/external/fmt/include/fmt/format-inl.h index d8c9c8a5ee..a887483b6f 100644 --- a/external/fmt/include/fmt/format-inl.h +++ b/external/fmt/include/fmt/format-inl.h @@ -8,42 +8,23 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ -#include -#include -#include -#include -#include -#include // for std::memmove -#include -#include - -#include "format.h" -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -# include -#endif - -#ifdef _WIN32 -# if !defined(NOMINMAX) && !defined(WIN32_LEAN_AND_MEAN) -# define NOMINMAX -# define WIN32_LEAN_AND_MEAN -# include -# undef WIN32_LEAN_AND_MEAN -# undef NOMINMAX -# else -# include +#ifndef FMT_MODULE +# include +# include // errno +# include +# include +# include + +# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +# include # endif -# include #endif -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable : 4702) // unreachable code +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) +# include // _isatty #endif -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } -inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } +#include "format.h" FMT_BEGIN_NAMESPACE namespace detail { @@ -57,95 +38,12 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) { std::terminate(); } -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -// A portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -FMT_FUNC int safe_strerror(int error_code, char*& buffer, - size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); - - class dispatcher { - private: - int error_code_; - char*& buffer_; - size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const dispatcher&) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - FMT_MAYBE_UNUSED - int handle(char* message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - FMT_MAYBE_UNUSED - int handle(detail::null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - FMT_MAYBE_UNUSED - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE - : result; - } - -#if !FMT_MSC_VER - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(detail::null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } -#endif - - public: - dispatcher(int err_code, char*& buf, size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } - }; - return dispatcher(error_code, buffer, buffer_size).run(); -} - FMT_FUNC void format_error_code(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + string_view message) noexcept { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential // bad_alloc. - out.resize(0); + out.try_resize(0); static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. @@ -156,1178 +54,1352 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); - auto it = std::back_inserter(out); + auto it = appender(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, "{}{}", message, SEP); - format_to(it, "{}{}", ERROR_STR, error_code); - assert(out.size() <= inline_buffer_size); + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); } FMT_FUNC void report_error(format_func func, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_fully because the latter may throw. - (void)std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); } // A wrapper around fwrite that throws on error. -FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); - if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); +inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); + if (written < count) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } -} // namespace detail - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -namespace detail { +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } -template Locale locale_ref::get() const { +template auto locale_ref::get() const -> Locale { static_assert(std::is_same::value, ""); return locale_ ? *static_cast(locale_) : std::locale(); } -template FMT_FUNC std::string grouping_impl(locale_ref loc) { - return std::use_facet>(loc.get()).grouping(); -} -template FMT_FUNC Char thousands_sep_impl(locale_ref loc) { - return std::use_facet>(loc.get()) - .thousands_sep(); +template +FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { + auto& facet = std::use_facet>(loc.get()); + auto grouping = facet.grouping(); + auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); + return {std::move(grouping), thousands_sep}; } -template FMT_FUNC Char decimal_point_impl(locale_ref loc) { +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { return std::use_facet>(loc.get()) .decimal_point(); } -} // namespace detail #else template -FMT_FUNC std::string detail::grouping_impl(locale_ref) { - return "\03"; -} -template FMT_FUNC Char detail::thousands_sep_impl(locale_ref) { - return FMT_STATIC_THOUSANDS_SEPARATOR; +FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result { + return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR}; } -template FMT_FUNC Char detail::decimal_point_impl(locale_ref) { +template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif -FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default; -FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default; +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#ifdef FMT_STATIC_THOUSANDS_SEPARATOR + value.visit(loc_writer<>{ + out, specs, std::string(1, FMT_STATIC_THOUSANDS_SEPARATOR), "\3", "."}); + return true; +#else + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return std::use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +#endif +} +} // namespace detail -FMT_FUNC void system_error::init(int err_code, string_view format_str, - format_args args) { - error_code_ = err_code; - memory_buffer buffer; - format_system_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error& base = *this; - base = std::runtime_error(to_string(buffer)); +FMT_FUNC void report_error(const char* message) { + FMT_THROW(format_error(message)); } -namespace detail { +template typename Locale::id format_facet::id; -template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { - // fallback_uintptr is always stored in little endian. - int i = static_cast(sizeof(void*)) - 1; - while (i > 0 && n.value[i] == 0) --i; - auto char_digits = std::numeric_limits::digits / 4; - return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; +#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +template format_facet::format_facet(Locale& loc) { + auto& numpunct = std::use_facet>(loc); + grouping_ = numpunct.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); } -template -const typename basic_data::digit_pair basic_data::digits[] = { - {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, - {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, - {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, - {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, - {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, - {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, - {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, - {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, - {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, - {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, - {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, - {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, - {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, - {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, - {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, - {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, - {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, - {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, - {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, - {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} +#endif -template -const char basic_data::hex_digits[] = "0123456789abcdef"; +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(fmt, args)); +} -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 +namespace detail { -template -const uint64_t basic_data::powers_of_10_64[] = { - 1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { + return x.f == y.f && x.e == y.e; +} -template -const uint32_t basic_data::zero_or_powers_of_10_32[] = {0, - FMT_POWERS_OF_10(1)}; +// Compilers should be able to optimize this into the ror instruction. +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { + r &= 31; + return (n >> r) | (n << (32 - r)); +} +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { + r &= 63; + return (n >> r) | (n << (64 - r)); +} -template -const uint64_t basic_data::zero_or_powers_of_10_64[] = { - 0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return umul128_upper64(static_cast(x) << 32, y); +} -// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. -// These are generated by support/compute-powers.py. -template -const uint64_t basic_data::pow10_significands[] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, -}; +// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint64_t high = x * y.high(); + uint128_fallback high_low = umul128(x, y.low()); + return {high + high_low.high(), high_low.low()}; +} -// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding -// to significands above. -template -const int16_t basic_data::pow10_exponents[] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return x * y; +} -template -const char basic_data::foreground_color[] = "\x1b[38;2;"; -template -const char basic_data::background_color[] = "\x1b[48;2;"; -template const char basic_data::reset_color[] = "\x1b[0m"; -template const wchar_t basic_data::wreset_color[] = L"\x1b[0m"; -template const char basic_data::signs[] = {0, '-', '+', ' '}; -template -const char basic_data::left_padding_shifts[] = {31, 31, 0, 1, 0}; -template -const char basic_data::right_padding_shifts[] = {0, 31, 0, 1, 0}; +// Various fast log computations. +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { + FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); + return (e * 631305 - 261663) >> 21; +} -template struct bits { - static FMT_CONSTEXPR_DECL const int value = - static_cast(sizeof(T) * std::numeric_limits::digits); -}; +FMT_INLINE_VARIABLE constexpr struct { + uint32_t divisor; + int shift_amount; +} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; + +// Replaces n by floor(n / pow(10, N)) returning true if and only if n is +// divisible by pow(10, N). +// Precondition: n <= pow(10, N + 1). +template +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { + // The numbers below are chosen such that: + // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, + // 2. nm mod 2^k < m if and only if n is divisible by d, + // where m is magic_number, k is shift_amount + // and d is divisor. + // + // Item 1 is a common technique of replacing division by a constant with + // multiplication, see e.g. "Division by Invariant Integers Using + // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set + // to ceil(2^k/d) for large enough k. + // The idea for item 2 originates from Schubfach. + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + n *= magic_number; + const uint32_t comparison_mask = (1u << info.shift_amount) - 1; + bool result = (n & comparison_mask) < magic_number; + n >>= info.shift_amount; + return result; +} -class fp; -template fp normalize(fp value); +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + return (n * magic_number) >> info.shift_amount; +} -// Lower (upper) boundary is a value half way between a floating-point value -// and its predecessor (successor). Boundaries have the same exponent as the -// value so only significands are stored. -struct boundaries { - uint64_t lower; - uint64_t upper; -}; +// Computes floor(n / 10^(kappa + 1)) (float) +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { + // 1374389535 = ceil(2^37/100) + return static_cast((static_cast(n) * 1374389535) >> 37); +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { + // 2361183241434822607 = ceil(2^(64+7)/1000) + return umul128_upper64(n, 2361183241434822607ull) >> 7; +} -// A handmade floating-point number f * pow(2, e). -class fp { - private: - using significand_type = uint64_t; +// Various subroutines using pow10 cache +template struct cache_accessor; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint64_t; + + static auto get_cached_power(int k) noexcept -> uint64_t { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + static constexpr const uint64_t pow10_significands[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, + 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, + 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, + 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, + 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, + 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; + return pow10_significands[k - float_info::min_k]; + } - public: - significand_type f; - int e; - - // All sizes are in bits. - // Subtract 1 to account for an implicit most significant bit in the - // normalized form. - static FMT_CONSTEXPR_DECL const int double_significand_size = - std::numeric_limits::digits - 1; - static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = - 1ULL << double_significand_size; - static FMT_CONSTEXPR_DECL const int significand_size = - bits::value; - - fp() : f(0), e(0) {} - fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} - - // Constructs fp from an IEEE754 double. It is a template to prevent compile - // errors on platforms where double is not IEEE754. - template explicit fp(Double d) { assign(d); } - - // Assigns d to this and return true iff predecessor is closer than successor. - template - bool assign(Double d) { - // Assume double is in the format [sign][exponent][significand]. - using limits = std::numeric_limits; - const int exponent_size = - bits::value - double_significand_size - 1; // -1 for sign - const uint64_t significand_mask = implicit_bit - 1; - const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - auto u = bit_cast(d); - f = u & significand_mask; - int biased_e = - static_cast((u & exponent_mask) >> double_significand_size); - // Predecessor is closer if d is a normalized power of 2 (f == 0) other than - // the smallest normalized number (biased_e > 1). - bool is_predecessor_closer = f == 0 && biased_e > 1; - if (biased_e != 0) - f += implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = biased_e - exponent_bias - double_significand_size; - return is_predecessor_closer; + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul96_upper64(u, cache); + return {static_cast(r >> 32), + static_cast(r) == 0}; } - template - bool assign(Double) { - *this = fp(); - return false; + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache >> (64 - 1 - beta)); } - // Assigns d to this together with computing lower and upper boundaries, - // where a boundary is a value half way between the number and its predecessor - // (lower) or successor (upper). The upper boundary is normalized and lower - // has the same exponent but may be not normalized. - template boundaries assign_with_boundaries(Double d) { - bool is_lower_closer = assign(d); - fp lower = - is_lower_closer ? fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); - // 1 in normalize accounts for the exponent shift above. - fp upper = normalize<1>(fp((f << 1) + 1, e - 1)); - lower.f <<= lower.e - upper.e; - return boundaries{lower.f, upper.f}; + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul96_lower64(two_f, cache); + return {((r >> (64 - beta)) & 1) != 0, + static_cast(r >> (32 - beta)) == 0}; } - template boundaries assign_float_with_boundaries(Double d) { - assign(d); - constexpr int min_normal_e = std::numeric_limits::min_exponent - - std::numeric_limits::digits; - significand_type half_ulp = 1 << (std::numeric_limits::digits - - std::numeric_limits::digits - 1); - if (min_normal_e > e) half_ulp <<= min_normal_e - e; - fp upper = normalize<0>(fp(f + half_ulp, e)); - fp lower = fp( - f - (half_ulp >> ((f == implicit_bit && e > min_normal_e) ? 1 : 0)), e); - lower.f <<= lower.e - upper.e; - return boundaries{lower.f, upper.f}; + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache - (cache >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta)); } -}; -// Normalizes the value converted from double and multiplied by (1 << SHIFT). -template fp normalize(fp value) { - // Handle subnormals. - const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; - while ((value.f & shifted_implicit_bit) == 0) { - value.f <<= 1; - --value.e; + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache + (cache >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta)); } - // Subtract 1 to account for hidden bit. - const auto offset = - fp::significand_size - fp::double_significand_size - SHIFT - 1; - value.f <<= offset; - value.e -= offset; - return value; -} -inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (static_cast( + cache >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; -// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { -#if FMT_USE_INT128 - auto product = static_cast<__uint128_t>(lhs) * rhs; - auto f = static_cast(product >> 64); - return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint128_fallback; + + static auto get_cached_power(int k) noexcept -> uint128_fallback { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + + static constexpr const uint128_fallback pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = lhs >> 32, b = lhs & mask; - uint64_t c = rhs >> 32, d = rhs & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} #endif -} - -inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } - -// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its -// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -inline fp get_cached_power(int min_exponent, int& pow10_exponent) { - const int64_t one_over_log2_10 = 0x4d104d42; // round(pow(2, 32) / log2(10)) - int index = static_cast( - ((min_exponent + fp::significand_size - 1) * one_over_log2_10 + - ((int64_t(1) << 32) - 1)) // ceil - >> 32 // arithmetic shift - ); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - return {data::pow10_significands[index], data::pow10_exponents[index]}; -} - -// A simple accumulator to hold the sums of terms in bigint::square if uint128_t -// is not available. -struct accumulator { - uint64_t lower; - uint64_t upper; - - accumulator() : lower(0), upper(0) {} - explicit operator uint32_t() const { return static_cast(lower); } + }; - void operator+=(uint64_t n) { - lower += n; - if (lower < n) ++upper; - } - void operator>>=(int shift) { - assert(shift == 32); - (void)shift; - lower = (upper << 32) | (lower >> 32); - upper >>= 32; +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info::min_k]; +#else + static constexpr const uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info::min_k; + int offset = k - kb; + + // Get base cache. + uint128_fallback base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); + uint128_fallback middle_low = umul128(base_cache.low(), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); + return {recovered_cache.high(), recovered_cache.low() + 1}; +#endif } -}; - -class bigint { - private: - // A bigint is stored as an array of bigits (big digits), with bigit at index - // 0 being the least significant one. - using bigit = uint32_t; - using double_bigit = uint64_t; - enum { bigits_capacity = 32 }; - basic_memory_buffer bigits_; - int exp_; - - bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } - bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } - static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; - - friend struct formatter; + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; - void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast((*this)[index]) - other - borrow; - (*this)[index] = static_cast(result); - borrow = static_cast(result >> (bigit_bits * 2 - 1)); + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul192_upper128(u, cache); + return {r.high(), r.low() == 0}; } - void remove_leading_zeros() { - int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; - bigits_.resize(to_unsigned(num_bigits + 1)); + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache.high() >> (64 - 1 - beta)); } - // Computes *this -= other assuming aligned bigints and *this >= other. - void subtract_aligned(const bigint& other) { - FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); - FMT_ASSERT(compare(*this, other) >= 0, ""); - bigit borrow = 0; - int i = other.exp_ - exp_; - for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) { - subtract_bigits(i, other.bigits_[j], borrow); - } - while (borrow > 0) subtract_bigits(i, 0, borrow); - remove_leading_zeros(); - } + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); - void multiply(uint32_t value) { - const double_bigit wide_value = value; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - double_bigit result = bigits_[i] * wide_value + carry; - bigits_[i] = static_cast(result); - carry = static_cast(result >> bigit_bits); - } - if (carry != 0) bigits_.push_back(carry); + auto r = umul192_lower128(two_f, cache); + return {((r.high() >> (64 - beta)) & 1) != 0, + ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } - void multiply(uint64_t value) { - const bigit mask = ~bigit(0); - const double_bigit lower = value & mask; - const double_bigit upper = value >> bigit_bits; - double_bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - double_bigit result = bigits_[i] * lower + (carry & mask); - carry = - bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits); - bigits_[i] = static_cast(result); - } - while (carry != 0) { - bigits_.push_back(carry & mask); - carry >>= bigit_bits; - } + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() - + (cache.high() >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta); } - public: - bigint() : exp_(0) {} - explicit bigint(uint64_t n) { assign(n); } - ~bigint() { assert(bigits_.capacity() <= bigits_capacity); } - - bigint(const bigint&) = delete; - void operator=(const bigint&) = delete; - - void assign(const bigint& other) { - auto size = other.bigits_.size(); - bigits_.resize(size); - auto data = other.bigits_.data(); - std::copy(data, data + size, make_checked(bigits_.data(), size)); - exp_ = other.exp_; + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() + + (cache.high() >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta); } - void assign(uint64_t n) { - size_t num_bigits = 0; - do { - bigits_[num_bigits++] = n & ~bigit(0); - n >>= bigit_bits; - } while (n != 0); - bigits_.resize(num_bigits); - exp_ = 0; + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; } +}; - int num_bigits() const { return static_cast(bigits_.size()) + exp_; } - - FMT_NOINLINE bigint& operator<<=(int shift) { - assert(shift >= 0); - exp_ += shift / bigit_bits; - shift %= bigit_bits; - if (shift == 0) return *this; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - bigit c = bigits_[i] >> (bigit_bits - shift); - bigits_[i] = (bigits_[i] << shift) + carry; - carry = c; - } - if (carry != 0) bigits_.push_back(carry); - return *this; - } +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { + return cache_accessor::get_cached_power(k); +} - template bigint& operator*=(Int value) { - FMT_ASSERT(value > 0, ""); - multiply(uint32_or_64_or_128_t(value)); - return *this; - } +// Various integer checks +template +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { + const int case_shorter_interval_left_endpoint_lower_threshold = 2; + const int case_shorter_interval_left_endpoint_upper_threshold = 3; + return exponent >= case_shorter_interval_left_endpoint_lower_threshold && + exponent <= case_shorter_interval_left_endpoint_upper_threshold; +} - friend int compare(const bigint& lhs, const bigint& rhs) { - int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); - if (num_lhs_bigits != num_rhs_bigits) - return num_lhs_bigits > num_rhs_bigits ? 1 : -1; - int i = static_cast(lhs.bigits_.size()) - 1; - int j = static_cast(rhs.bigits_.size()) - 1; - int end = i - j; - if (end < 0) end = 0; - for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; - if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; - } - if (i != j) return i > j ? 1 : -1; - return 0; +// Remove trailing zeros from n and return the number of zeros removed (float) +FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { + FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; } - - // Returns compare(lhs1 + lhs2, rhs). - friend int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { - int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); - int num_rhs_bigits = rhs.num_bigits(); - if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; - if (max_lhs_bigits > num_rhs_bigits) return 1; - auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; - }; - double_bigit borrow = 0; - int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); - for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { - double_bigit sum = - static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); - bigit rhs_bigit = get_bigit(rhs, i); - if (sum > rhs_bigit + borrow) return 1; - borrow = rhs_bigit + borrow - sum; - if (borrow > 1) return -1; - borrow <<= bigit_bits; - } - return borrow != 0 ? -1 : 0; + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; } + return s; +} - // Assigns pow(10, exp) to this bigint. - void assign_pow10(int exp) { - assert(exp >= 0); - if (exp == 0) return assign(1); - // Find the top bit. - int bitmask = 1; - while (exp >= bitmask) bitmask <<= 1; - bitmask >>= 1; - // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by - // repeated squaring and multiplication. - assign(5); - bitmask >>= 1; - while (bitmask != 0) { - square(); - if ((exp & bitmask) != 0) *this *= 5; - bitmask >>= 1; - } - *this <<= exp; // Multiply by pow(2, exp) by shifting. +// Removes trailing zeros and returns the number of zeros removed (double) +FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { + FMT_ASSERT(n != 0, ""); + + // This magic number is ceil(2^90 / 10^8). + constexpr uint64_t magic_number = 12379400392853802749ull; + auto nm = umul128(n, magic_number); + + // Is n is divisible by 10^8? + if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { + // If yes, work with the quotient... + auto n32 = static_cast(nm.high() >> (90 - 64)); + // ... and use the 32 bit variant of the function + int s = remove_trailing_zeros(n32, 8); + n = n32; + return s; } - void square() { - basic_memory_buffer n(std::move(bigits_)); - int num_bigits = static_cast(bigits_.size()); - int num_result_bigits = 2 * num_bigits; - bigits_.resize(to_unsigned(num_result_bigits)); - using accumulator_t = conditional_t; - auto sum = accumulator_t(); - for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { - // Compute bigit at position bigit_index of the result by adding - // cross-product terms n[i] * n[j] such that i + j == bigit_index. - for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { - // Most terms are multiplied twice which can be optimized in the future. - sum += static_cast(n[i]) * n[j]; - } - (*this)[bigit_index] = static_cast(sum); - sum >>= bits::value; // Compute the carry. - } - // Do the same for the top half. - for (int bigit_index = num_bigits; bigit_index < num_result_bigits; - ++bigit_index) { - for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) - sum += static_cast(n[i++]) * n[j--]; - (*this)[bigit_index] = static_cast(sum); - sum >>= bits::value; - } - --num_result_bigits; - remove_leading_zeros(); - exp_ *= 2; - } + // If n is not divisible by 10^8, work with n itself. + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 - // Divides this bignum by divisor, assigning the remainder to this and - // returning the quotient. - int divmod_assign(const bigint& divisor) { - FMT_ASSERT(this != &divisor, ""); - if (compare(*this, divisor) < 0) return 0; - int num_bigits = static_cast(bigits_.size()); - FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); - int exp_difference = exp_ - divisor.exp_; - if (exp_difference > 0) { - // Align bigints by adding trailing zeros to simplify subtraction. - bigits_.resize(to_unsigned(num_bigits + exp_difference)); - for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) - bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); - exp_ -= exp_difference; - } - int quotient = 0; - do { - subtract_aligned(divisor); - ++quotient; - } while (compare(*this, divisor) >= 0); - return quotient; + int s = 0; + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; } -}; - -enum class round_direction { unknown, up, down }; - -// Given the divisor (normally a power of 10), the remainder = v % divisor for -// some number v and the error, returns whether v should be rounded up, down, or -// whether the rounding direction can't be determined due to error. -// error should be less than divisor / 2. -inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, - uint64_t error) { - FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. - FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. - FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. - // Round down if (remainder + error) * 2 <= divisor. - if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) - return round_direction::down; - // Round up if (remainder - error) * 2 >= divisor. - if (remainder >= error && - remainder - error >= divisor - (remainder - error)) { - return round_direction::up; + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; } - return round_direction::unknown; -} - -namespace digits { -enum result { - more, // Generate more digits. - done, // Done generating digits. - error // Digit generation cancelled due to an error. -}; -} -// A version of count_digits optimized for grisu_gen_digits. -inline int grisu_count_digits(uint32_t n) { - if (n < 10) return 1; - if (n < 100) return 2; - if (n < 1000) return 3; - if (n < 10000) return 4; - if (n < 100000) return 5; - if (n < 1000000) return 6; - if (n < 10000000) return 7; - if (n < 100000000) return 8; - if (n < 1000000000) return 9; - return 10; + return s; } -// Generates output using the Grisu digit-gen algorithm. -// error: the size of the region (lower, upper) outside of which numbers -// definitely do not round to value (Delta in Grisu3). -template -FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, - int& exp, Handler& handler) { - const fp one(1ULL << -value.e, value.e); - // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be - // zero because it contains a product of two 64-bit numbers with MSB set (due - // to normalization) - 1, shifted right by at most 60 bits. - auto integral = static_cast(value.f >> -one.e); - FMT_ASSERT(integral != 0, ""); - FMT_ASSERT(integral == value.f >> -one.e, ""); - // The fractional part of scaled value (p2 in Grisu) c = value % one. - uint64_t fractional = value.f & (one.f - 1); - exp = grisu_count_digits(integral); // kappa in Grisu. - // Divide by 10 to prevent overflow. - auto result = handler.on_start(data::powers_of_10_64[exp - 1] << -one.e, - value.f / 10, error * 10, exp); - if (result != digits::more) return result; - // Generate digits for the integral part. This can produce up to 10 digits. - do { - uint32_t digit = 0; - auto divmod_integral = [&](uint32_t divisor) { - digit = integral / divisor; - integral %= divisor; - }; - // This optimization by Milo Yip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: - divmod_integral(1000000000); - break; - case 9: - divmod_integral(100000000); - break; - case 8: - divmod_integral(10000000); - break; - case 7: - divmod_integral(1000000); - break; - case 6: - divmod_integral(100000); - break; - case 5: - divmod_integral(10000); - break; - case 4: - divmod_integral(1000); - break; - case 3: - divmod_integral(100); - break; - case 2: - divmod_integral(10); - break; - case 1: - digit = integral; - integral = 0; - break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - --exp; - uint64_t remainder = - (static_cast(integral) << -one.e) + fractional; - result = handler.on_digit(static_cast('0' + digit), - data::powers_of_10_64[exp] << -one.e, remainder, - error, exp, true); - if (result != digits::more) return result; - } while (exp > 0); - // Generate digits for the fractional part. - for (;;) { - fractional *= 10; - error *= 10; - char digit = - static_cast('0' + static_cast(fractional >> -one.e)); - fractional &= one.f - 1; - --exp; - result = handler.on_digit(digit, one.f, fractional, error, exp, false); - if (result != digits::more) return result; +// The main algorithm for shorter interval case +template +FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { + decimal_fp ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor::cache_entry_type; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + + auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( + cache, beta); + auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( + cache, beta); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; } -} -// The fixed precision digit handler. -struct fixed_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error, - int& exp) { - // Non-fixed formats require at least one digit and no precision adjustment. - if (!fixed) return digits::more; - // Adjust fixed precision by exponent because it is relative to decimal - // point. - precision += exp + exp10; - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (precision > 0) return digits::more; - if (precision < 0) return digits::done; - auto dir = get_round_direction(divisor, remainder, error); - if (dir == round_direction::unknown) return digits::error; - buf[size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor::compute_round_up_for_shorter_interval_case(cache, + beta); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info::shorter_interval_tie_lower_threshold && + exponent <= float_info::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; } + return ret_value; +} - digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, - uint64_t error, int, bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - buf[size++] = '0'; - } - return digits::done; - } -}; +template auto to_decimal(T x) noexcept -> decimal_fp { + // Step 1: integer promotion & Schubfach multiplier calculation. -// The shortest representation digit handler. -struct grisu_shortest_handler { - char* buf; - int size; - // Distance between scaled value and upper bound (wp_W in Grisu3). - uint64_t diff; + using carrier_uint = typename float_info::carrier_uint; + using cache_entry_type = typename cache_accessor::cache_entry_type; + auto br = bit_cast(x); - digits::result on_start(uint64_t, uint64_t, uint64_t, int&) { - return digits::more; - } + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = + static_cast((br & exponent_mask()) >> num_significand_bits()); - // Decrement the generated number approaching value from above. - void round(uint64_t d, uint64_t divisor, uint64_t& remainder, - uint64_t error) { - while ( - remainder < d && error - remainder >= divisor && - (remainder + divisor < d || d - remainder >= remainder + divisor - d)) { - --buf[size - 1]; - remainder += divisor; - } - } + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); - // Implements Grisu's round_weed. - digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder, - uint64_t error, int exp, bool integral) { - buf[size++] = digit; - if (remainder >= error) return digits::more; - uint64_t unit = integral ? 1 : data::powers_of_10_64[-exp]; - uint64_t up = (diff - 1) * unit; // wp_Wup - round(up, divisor, remainder, error); - uint64_t down = (diff + 1) * unit; // wp_Wdown - if (remainder < down && error - remainder >= divisor && - (remainder + divisor < down || - down - remainder > remainder + divisor - down)) { - return digits::error; - } - return 2 * unit <= remainder && remainder <= error - 4 * unit - ? digits::done - : digits::error; - } -}; + // Shorter interval case; proceed like Schubfach. + // In fact, when exponent == 1 and significand == 0, the interval is + // regular. However, it can be shown that the end-results are anyway same. + if (significand == 0) return shorter_interval_case(exponent); -// Formats value using a variation of the Fixed-Precision Positive -// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: -// https://fmt.dev/p372-steele.pdf. -template -void fallback_format(Double d, buffer& buf, int& exp10) { - bigint numerator; // 2 * R in (FPP)^2. - bigint denominator; // 2 * S in (FPP)^2. - // lower and upper are differences between value and corresponding boundaries. - bigint lower; // (M^- in (FPP)^2). - bigint upper_store; // upper's value if different from lower. - bigint* upper = nullptr; // (M^+ in (FPP)^2). - fp value; - // Shift numerator and denominator by an extra bit or two (if lower boundary - // is closer) to make lower and upper integers. This eliminates multiplication - // by 2 during later computations. - // TODO: handle float - int shift = value.assign(d) ? 2 : 1; - uint64_t significand = value.f << shift; - if (value.e >= 0) { - numerator.assign(significand); - numerator <<= value.e; - lower.assign(1); - lower <<= value.e; - if (shift != 1) { - upper_store.assign(1); - upper_store <<= value.e + 1; - upper = &upper_store; - } - denominator.assign_pow10(exp10); - denominator <<= 1; - } else if (exp10 < 0) { - numerator.assign_pow10(-exp10); - lower.assign(numerator); - if (shift != 1) { - upper_store.assign(numerator); - upper_store <<= 1; - upper = &upper_store; - } - numerator *= significand; - denominator.assign(1); - denominator <<= shift - value.e; + significand |= (static_cast(1) << num_significand_bits()); } else { - numerator.assign(significand); - denominator.assign_pow10(exp10); - denominator <<= shift - value.e; - lower.assign(1); - if (shift != 1) { - upper_store.assign(1ULL << 1); - upper = &upper_store; - } - } - if (!upper) upper = &lower; - // Invariant: value == (numerator / denominator) * pow(10, exp10). - bool even = (value.f & 1) == 0; - int num_digits = 0; - char* data = buf.data(); - for (;;) { - int digit = numerator.divmod_assign(denominator); - bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. - // numerator + upper >[=] pow10: - bool high = add_compare(numerator, *upper, denominator) + even > 0; - data[num_digits++] = static_cast('0' + digit); - if (low || high) { - if (!low) { - ++data[num_digits - 1]; - } else if (high) { - int result = add_compare(numerator, numerator, denominator); - // Round half to even. - if (result > 0 || (result == 0 && (digit % 2) != 0)) - ++data[num_digits - 1]; - } - buf.resize(to_unsigned(num_digits)); - exp10 -= num_digits - 1; - return; - } - numerator *= 10; - lower *= 10; - if (upper != &lower) *upper *= 10; - } -} - -// Formats value using the Grisu algorithm -// (https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf) -// if T is a IEEE754 binary32 or binary64 and snprintf otherwise. -template -int format_float(T value, int precision, float_specs specs, buffer& buf) { - static_assert(!std::is_same::value, ""); - FMT_ASSERT(value >= 0, "value is negative"); - - const bool fixed = specs.format == float_format::fixed; - if (value <= 0) { // <= instead of == to silence a warning. - if (precision <= 0 || !fixed) { - buf.push_back('0'); - return 0; - } - buf.resize(to_unsigned(precision)); - std::uninitialized_fill_n(buf.data(), precision, '0'); - return -precision; + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = + std::numeric_limits::min_exponent - num_significand_bits() - 1; } - if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); - - int exp = 0; - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - if (precision < 0) { - fp fp_value; - auto boundaries = specs.binary32 - ? fp_value.assign_float_with_boundaries(value) - : fp_value.assign_with_boundaries(value); - fp_value = normalize(fp_value); - // Find a cached power of 10 such that multiplying value by it will bring - // the exponent in the range [min_exp, -32]. - const fp cached_pow = get_cached_power( - min_exp - (fp_value.e + fp::significand_size), cached_exp10); - // Multiply value and boundaries by the cached power of 10. - fp_value = fp_value * cached_pow; - boundaries.lower = multiply(boundaries.lower, cached_pow.f); - boundaries.upper = multiply(boundaries.upper, cached_pow.f); - assert(min_exp <= fp_value.e && fp_value.e <= -32); - --boundaries.lower; // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}. - ++boundaries.upper; // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}. - // Numbers outside of (lower, upper) definitely do not round to value. - grisu_shortest_handler handler{buf.data(), 0, - boundaries.upper - fp_value.f}; - auto result = - grisu_gen_digits(fp(boundaries.upper, fp_value.e), - boundaries.upper - boundaries.lower, exp, handler); - if (result == digits::error) { - exp += handler.size - cached_exp10 - 1; - fallback_format(value, buf, exp); - return exp; + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai. + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor::compute_delta(cache, beta); + const carrier_uint two_fc = significand << 1; + + // For the case of binary32, the result of integer check is not correct for + // 29711844 * 2^-82 + // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + // and 29711844 * 2^-81 + // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + // and they are the unique counterexamples. However, since 29711844 is even, + // this does not cause any problem for the endpoints calculations; it can only + // cause a problem when we need to perform integer check for the center. + // Fortunately, with these inputs, that branch is never executed, so we are + // fine. + const typename cache_accessor::compute_mul_result z_mul = + cache_accessor::compute_mul((two_fc | 1) << beta, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary. + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here. + decimal_fp ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); + uint32_t r = static_cast(z_mul.result - float_info::big_divisor * + ret_value.significand); + + if (r < deltai) { + // Exclude the right endpoint if necessary. + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { + --ret_value.significand; + r = float_info::big_divisor; + goto small_divisor_case_label; } - buf.resize(to_unsigned(handler.size)); + } else if (r > deltai) { + goto small_divisor_case_label; } else { - if (precision > 17) return snprintf_float(value, precision, specs, buf); - fp normalized = normalize(fp(value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::significand_size), cached_exp10); - normalized = normalized * cached_pow; - fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) - return snprintf_float(value, precision, specs, buf); - int num_digits = handler.size; - if (!fixed) { - // Remove trailing zeros. - while (num_digits > 0 && buf[num_digits - 1] == '0') { - --num_digits; - ++exp; - } - } - buf.resize(to_unsigned(num_digits)); - } - return exp - cached_exp10; -} + // r == deltai; compare fractional parts. + const typename cache_accessor::compute_mul_parity_result x_mul = + cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); -template -int snprintf_float(T value, int precision, float_specs specs, - buffer& buf) { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - static_assert(!std::is_same::value, ""); - - // Subtract 1 to account for the difference in precision since we use %e for - // both general and exponent format. - if (specs.format == float_format::general || - specs.format == float_format::exp) - precision = (precision >= 0 ? precision : 6) - 1; - - // Build the format string. - enum { max_format_size = 7 }; // The longest format is "%#.*Le". - char format[max_format_size]; - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint && specs.format == float_format::hex) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same()) *format_ptr++ = 'L'; - *format_ptr++ = specs.format != float_format::hex - ? (specs.format == float_format::fixed ? 'f' : 'e') - : (specs.upper ? 'A' : 'a'); - *format_ptr = '\0'; - - // Format using snprintf. - auto offset = buf.size(); - for (;;) { - auto begin = buf.data() + offset; - auto capacity = buf.capacity() - offset; -#ifdef FMT_FUZZ - if (precision > 100000) - throw std::runtime_error( - "fuzz mode - avoid large allocation inside snprintf"); -#endif - // Suppress the warning about a nonliteral format string. - // Cannot use auto because of a bug in MinGW (#1532). - int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; - int result = precision >= 0 - ? snprintf_ptr(begin, capacity, format, precision, value) - : snprintf_ptr(begin, capacity, format, value); - if (result < 0) { - buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially. - continue; - } - auto size = to_unsigned(result); - // Size equal to capacity means that the last character was truncated. - if (size >= capacity) { - buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. - continue; - } - auto is_digit = [](char c) { return c >= '0' && c <= '9'; }; - if (specs.format == float_format::fixed) { - if (precision == 0) { - buf.resize(size); - return 0; - } - // Find and remove the decimal point. - auto end = begin + size, p = end; - do { - --p; - } while (is_digit(*p)); - int fraction_size = static_cast(end - p - 1); - std::memmove(p, p + 1, to_unsigned(fraction_size)); - buf.resize(size - 1); - return -fraction_size; - } - if (specs.format == float_format::hex) { - buf.resize(size + offset); - return 0; - } - // Find and parse the exponent. - auto end = begin + size, exp_pos = end; - do { - --exp_pos; - } while (*exp_pos != 'e'); - char sign = exp_pos[1]; - assert(sign == '+' || sign == '-'); - int exp = 0; - auto p = exp_pos + 2; // Skip 'e' and sign. - do { - assert(is_digit(*p)); - exp = exp * 10 + (*p++ - '0'); - } while (p != end); - if (sign == '-') exp = -exp; - int fraction_size = 0; - if (exp_pos != begin + 1) { - // Remove trailing zeros. - auto fraction_end = exp_pos - 1; - while (*fraction_end == '0') --fraction_end; - // Move the fractional part left to get rid of the decimal point. - fraction_size = static_cast(fraction_end - begin - 1); - std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); - } - buf.resize(to_unsigned(fraction_size) + offset + 1); - return exp - fraction_size; + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) + goto small_divisor_case_label; } + ret_value.exponent = minus_k + float_info::kappa + 1; + + // We may need to remove trailing zeros. + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor. + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info::kappa; + + uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + + // Is dist divisible by 10^kappa? + const bool divisible_by_small_divisor = + check_divisibility_and_divide_by_pow10::kappa>(dist); + + // Add dist / 10^kappa to the significand. + ret_value.significand += dist; + + if (!divisible_by_small_divisor) return ret_value; + + // Check z^(f) >= epsilon^(f). + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number. + const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); + + // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), + // or equivalently, when y is an integer. + if (y_mul.parity != approx_y_parity) + --ret_value.significand; + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) + --ret_value.significand; + return ret_value; } - -// A public domain branchless UTF-8 decoder by Christopher Wellons: -// https://github.com/skeeto/branchless-utf8 -/* Decode the next character, c, from buf, reporting errors in e. - * - * Since this is a branchless decoder, four bytes will be read from the - * buffer regardless of the actual length of the next character. This - * means the buffer _must_ have at least three bytes of zero padding - * following the end of the data stream. - * - * Errors are reported in e, which will be non-zero if the parsed - * character was somehow invalid: invalid byte sequence, non-canonical - * encoding, or a surrogate half. - * - * The function returns a pointer to the next character. When an error - * occurs, this pointer will be a guess that depends on the particular - * error, but it will always advance at least one byte. - */ -FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) { - static const char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 2, 2, 2, 2, 3, 3, 4, 0}; - static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; - static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; - static const int shiftc[] = {0, 18, 12, 6, 0}; - static const int shifte[] = {0, 6, 4, 2, 0}; - - auto s = reinterpret_cast(buf); - int len = lengths[s[0] >> 3]; - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - const char* next = buf + len + !len; - - // Assume a four-byte character and load four bytes. Unused bits are - // shifted out. - *c = uint32_t(s[0] & masks[len]) << 18; - *c |= uint32_t(s[1] & 0x3f) << 12; - *c |= uint32_t(s[2] & 0x3f) << 6; - *c |= uint32_t(s[3] & 0x3f) << 0; - *c >>= shiftc[len]; - - // Accumulate the various error conditions. - *e = (*c < mins[len]) << 6; // non-canonical encoding - *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? - *e |= (*c > 0x10FFFF) << 8; // out of range? - *e |= (s[1] & 0xc0) >> 2; - *e |= (s[2] & 0xc0) >> 4; - *e |= (s[3]) >> 6; - *e ^= 0x2a; // top two bits of each tail byte correct? - *e >>= shifte[len]; - - return next; -} +} // namespace dragonbox } // namespace detail template <> struct formatter { - format_parse_context::iterator parse(format_parse_context& ctx) { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { return ctx.begin(); } - format_context::iterator format(const detail::bigint& n, - format_context& ctx) { + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, "{:x}", value); + out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, "{:08x}", value); + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits); + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { - auto transcode = [this](const char* p) { - auto cp = uint32_t(); - auto error = 0; - p = utf8_decode(p, &cp, &error); - if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); + for_each_codepoint(s, [this](uint32_t cp, string_view) { + if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); if (cp <= 0xFFFF) { buffer_.push_back(static_cast(cp)); } else { @@ -1335,119 +1407,522 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { buffer_.push_back(static_cast(0xD800 + (cp >> 10))); buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); } - return p; - }; - auto p = s.data(); - const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. - if (s.size() >= block_size) { - for (auto end = p + s.size() - block_size + 1; p < end;) p = transcode(p); - } - if (auto num_chars_left = s.data() + s.size() - p) { - char buf[2 * block_size - 1] = {}; - memcpy(buf, p, to_unsigned(num_chars_left)); - p = buf; - do { - p = transcode(p); - } while (p - buf < num_chars_left); - } + return true; + }); buffer_.push_back(0); } FMT_FUNC void format_system_error(detail::buffer& out, int error_code, - string_view message) FMT_NOEXCEPT { + const char* message) noexcept { FMT_TRY { - memory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - char* system_message = &buf[0]; - int result = - detail::safe_strerror(error_code, system_message, buf.size()); - if (result == 0) { - format_to(std::back_inserter(out), "{}: {}", message, system_message); - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } + auto ec = std::error_code(error_code, std::generic_category()); + detail::write(appender(out), std::system_error(ec, message).what()); + return; } FMT_CATCH(...) {} format_error_code(out, error_code, message); } -FMT_FUNC void detail::error_handler::on_error(const char* message) { - FMT_THROW(format_error(message)); -} - FMT_FUNC void report_system_error(int error_code, - fmt::string_view message) FMT_NOEXCEPT { + const char* message) noexcept { report_error(format_system_error, error_code, message); } -struct stringifier { - template FMT_INLINE std::string operator()(T value) const { - return to_string(value); +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { + // Don't optimize the "{}" case to keep the binary size small and because it + // can be better optimized in fmt::format anyway. + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + return to_string(buffer); +} + +namespace detail { + +template struct span { + T* data; + size_t size; +}; + +template auto flockfile(F* f) -> decltype(_lock_file(f)) { + _lock_file(f); +} +template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { + _unlock_file(f); +} + +#ifndef getc_unlocked +template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { + return _fgetc_nolock(f); +} +#endif + +template +struct has_flockfile : std::false_type {}; + +template +struct has_flockfile()))>> + : std::true_type {}; + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; } - std::string operator()(basic_format_arg::handle h) const { - memory_buffer buf; - detail::buffer& base = buf; - format_parse_context parse_ctx({}); - format_context format_ctx(std::back_inserter(base), {}, {}); - h.format(parse_ctx, format_ctx); - return to_string(buf); + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); } + + void flush() { fflush(this->file_); } }; -FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) { - if (format_str.size() == 2 && equal2(format_str.data(), "{}")) { - auto arg = args.get(0); - if (!arg) error_handler().on_error("argument not found"); - return visit_format_arg(stringifier(), arg); +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; } - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); - return to_string(buffer); -} -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); -#ifdef _WIN32 - auto fd = _fileno(f); - if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); - auto written = DWORD(); - if (!WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), - u16.c_str(), static_cast(u16.size()), &written, - nullptr)) { - FMT_THROW(format_error("failed to write to console")); - } - return; + void init_buffer() { + if (this->file_->_IO_write_ptr) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +#ifndef FMT_USE_FALLBACK_FILE +# define FMT_USE_FALLBACK_FILE 1 #endif - detail::fwrite_fully(buffer.data(), 1, buffer.size(), f); + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} + +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +template +class file_print_buffer : public buffer { + public: + explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} +}; + +template +class file_print_buffer::value>> + : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + F* f = file_; // Make funlockfile depend on the template parameter F + funlockfile(f); // for the system API detection to work. + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +#else +using dword = conditional_t; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); + +FMT_FUNC bool write_console(int fd, string_view text) { + auto u16 = utf8_to_utf16(text); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), nullptr, nullptr) != 0; } +#endif #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_fully(buffer.data(), buffer.size(), f); } #endif -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); +FMT_FUNC void print(std::FILE* f, string_view text) { +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_fully(text.data(), text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::print(f, {buffer.data(), buffer.size()}); } -FMT_END_NAMESPACE +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) + return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer<>(f); + return detail::vformat_to(buffer, fmt, args); +} -#ifdef _MSC_VER -# pragma warning(pop) -#endif +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); +} + +namespace detail { + +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// This code is generated by support/printable.py. +FMT_FUNC auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +} // namespace detail + +FMT_END_NAMESPACE #endif // FMT_FORMAT_INL_H_ diff --git a/external/fmt/include/fmt/format.h b/external/fmt/include/fmt/format.h index 721ea9bf8d..67f0ab739b 100644 --- a/external/fmt/include/fmt/format.h +++ b/external/fmt/include/fmt/format.h @@ -1,60 +1,97 @@ /* - Formatting library for C++ - - Copyright (c) 2012 - present, Victor Zverovich - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --- Optional exception to the license --- - - As an exception, if, as a result of your compiling your source code, portions - of this Software are embedded into a machine-executable object form of such - source code, you may redistribute such embedded portions in such object form - without including the above copyright and permission notices. + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include -#include -#include -#include -#include -#include -#include +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES +#endif + +#include "base.h" + +#ifndef FMT_MODULE +# include // std::signbit +# include // uint32_t +# include // std::memcpy +# include // std::initializer_list +# include // std::numeric_limits +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error + +// Checking FMT_CPLUSPLUS for warning suppression in MSVC. +# if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include // std::bit_cast +# endif -#include "core.h" +// libc++ supports string_view in pre-c++17. +# if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# include +# define FMT_USE_STRING_VIEW +# endif +#endif // FMT_MODULE -#ifdef __INTEL_COMPILER -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline #else -# define FMT_ICC_VERSION 0 +# define FMT_INLINE_VARIABLE +#endif + +#ifndef FMT_NO_UNIQUE_ADDRESS +# if FMT_CPLUSPLUS >= 202002L +# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +# elif (FMT_MSC_VERSION >= 1929) && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +# endif +# endif +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS #endif -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else -# define FMT_CUDA_VERSION 0 +# define FMT_SO_VISIBILITY(value) #endif #ifdef __has_builtin @@ -69,33 +106,16 @@ # define FMT_NOINLINE #endif -#if __cplusplus == 201103L || __cplusplus == 201402L -# if defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -# elif FMT_GCC_VERSION >= 700 && !defined(__PGI) && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -# else -# define FMT_FALLTHROUGH -# endif -#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \ - (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define FMT_FALLTHROUGH [[fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif +namespace std { +template <> struct iterator_traits { + using iterator_category = output_iterator_tag; + using value_type = char; +}; +} // namespace std #ifndef FMT_THROW # if FMT_EXCEPTIONS -# if FMT_MSC_VER || FMT_NVCC +# if FMT_MSC_VERSION || defined(__NVCC__) FMT_BEGIN_NAMESPACE namespace detail { template inline void do_throw(const Exception& x) { @@ -111,26 +131,26 @@ FMT_END_NAMESPACE # define FMT_THROW(x) throw x # endif # else -# define FMT_THROW(x) \ - do { \ - static_cast(sizeof(x)); \ - FMT_ASSERT(false, ""); \ - } while (false) +# define FMT_THROW(x) \ + ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) # endif #endif -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif #endif #ifndef FMT_USE_USER_DEFINED_LITERALS // EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ - FMT_MSC_VER >= 1900) && \ +// +// GCC before 4.9 requires a space in `operator"" _a` which is invalid in later +// compiler versions. +# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 409 || \ + FMT_MSC_VERSION >= 1900) && \ (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) # define FMT_USE_USER_DEFINED_LITERALS 1 # else @@ -138,475 +158,677 @@ FMT_END_NAMESPACE # endif #endif -#ifndef FMT_USE_UDL_TEMPLATE -// EDG frontend based compilers (icc, nvcc, etc) and GCC < 6.4 do not properly -// support UDL templates and GCC >= 9 warns about them. -# if FMT_USE_USER_DEFINED_LITERALS && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 501) && \ - ((FMT_GCC_VERSION >= 604 && __cplusplus >= 201402L) || \ - FMT_CLANG_VERSION >= 304) -# define FMT_USE_UDL_TEMPLATE 1 -# else -# define FMT_USE_UDL_TEMPLATE 0 -# endif -#endif - -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif #endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 +// __builtin_ctz is broken in Intel Compiler Classic on Windows: +// https://github.com/fmtlib/fmt/issues/2510. +#ifndef __ICL +# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ + defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ + FMT_ICC_VERSION || defined(__NVCOMPILER) +# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) +# endif #endif -// __builtin_clz is broken in clang with Microsoft CodeGen: -// https://github.com/fmtlib/fmt/issues/519 -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) -#endif -#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +#if FMT_MSC_VERSION +# include // _BitScanReverse[64], _BitScanForward[64], _umul128 #endif // Some compilers masquerade as both MSVC and GCC-likes or otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) -# include // _BitScanReverse, _BitScanReverse64 - +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ + !defined(FMT_BUILTIN_CTZLL) FMT_BEGIN_NAMESPACE namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# ifndef __clang__ +# if !defined(__clang__) +# pragma intrinsic(_BitScanForward) # pragma intrinsic(_BitScanReverse) +# if defined(_WIN64) +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# endif # endif -inline uint32_t clz(uint32_t x) { + +inline auto clz(uint32_t x) -> int { unsigned long r = 0; _BitScanReverse(&r, x); - FMT_ASSERT(x != 0, ""); // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. - FMT_SUPPRESS_MSC_WARNING(6102) - return 31 - r; + FMT_MSC_WARNING(suppress : 6102) + return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) -# if defined(_WIN64) && !defined(__clang__) -# pragma intrinsic(_BitScanReverse64) -# endif - -inline uint32_t clzll(uint64_t x) { +inline auto clzll(uint64_t x) -> int { unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 - (r + 32); - + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif - FMT_ASSERT(x != 0, ""); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. - FMT_SUPPRESS_MSC_WARNING(6102) - return 63 - r; + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) + +inline auto ctz(uint32_t x) -> int { + unsigned long r = 0; + _BitScanForward(&r, x); + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + return static_cast(r); +} +# define FMT_BUILTIN_CTZ(n) detail::ctz(n) + +inline auto ctzll(uint64_t x) -> int { + unsigned long r = 0; + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. +# ifdef _WIN64 + _BitScanForward64(&r, x); +# else + // Scan the low 32 bits. + if (_BitScanForward(&r, static_cast(x))) return static_cast(r); + // Scan the high 32 bits. + _BitScanForward(&r, static_cast(x >> 32)); + r += 32; +# endif + return static_cast(r); +} +# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) } // namespace detail FMT_END_NAMESPACE #endif -// Enable the deprecated numeric alignment. -#ifndef FMT_DEPRECATED_NUMERIC_ALIGN -# define FMT_DEPRECATED_NUMERIC_ALIGN 0 -#endif - FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + namespace detail { -// An equivalent of `*reinterpret_cast(&source)` that doesn't have -// undefined behavior (e.g. due to type aliasing). -// Example: uint64_t d = bit_cast(2.718); -template -inline Dest bit_cast(const Source& source) { - static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); - Dest dest; - std::memcpy(&dest, &source, sizeof(dest)); - return dest; +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#else +template struct std_string_view {}; +#endif + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; } -inline bool is_big_endian() { - const auto u = 1u; +inline auto is_big_endian() -> bool { +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else struct bytes { - char data[sizeof(u)]; + char data[sizeof(int)]; }; - return bit_cast(u).data[0] == 0; + return bit_cast(1).data[0] == 0; +#endif } -// A fallback implementation of uintptr_t for systems that lack it. -struct fallback_uintptr { - unsigned char value[sizeof(void*)]; +class uint128_fallback { + private: + uint64_t lo_, hi_; - fallback_uintptr() = default; - explicit fallback_uintptr(const void* p) { - *this = bit_cast(p); - if (is_big_endian()) { - for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j) - std::swap(value[i], value[j]); + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } + friend auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } + + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; } }; + +using uint128_t = conditional_t; + #ifdef UINTPTR_MAX using uintptr_t = ::uintptr_t; -inline uintptr_t to_uintptr(const void* p) { return bit_cast(p); } #else -using uintptr_t = fallback_uintptr; -inline fallback_uintptr to_uintptr(const void* p) { - return fallback_uintptr(p); -} +using uintptr_t = uint128_t; #endif // Returns the largest possible value for type T. Same as // std::numeric_limits::max() but shorter and not affected by the max macro. -template constexpr T max_value() { +template constexpr auto max_value() -> T { return (std::numeric_limits::max)(); } -template constexpr int num_bits() { +template constexpr auto num_bits() -> int { return std::numeric_limits::digits; } // std::numeric_limits::digits may return 0 for 128-bit ints. -template <> constexpr int num_bits() { return 128; } -template <> constexpr int num_bits() { return 128; } -template <> constexpr int num_bits() { - return static_cast(sizeof(void*) * - std::numeric_limits::digits); +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); + struct data_t { + unsigned value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); } FMT_INLINE void assume(bool condition) { (void)condition; -#if FMT_HAS_BUILTIN(__builtin_assume) +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); #endif } -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; - -template -using void_t = typename detail::void_t_impl::type; - // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); template using sentinel_t = decltype(std::end(std::declval())); -// Detect the iterator category of *any* given type in a SFINAE-friendly way. -// Unfortunately, older implementations of std::iterator_traits are not safe -// for use in a SFINAE-context. -template -struct iterator_category : std::false_type {}; - -template struct iterator_category { - using type = std::random_access_iterator_tag; -}; - -template -struct iterator_category> { - using type = typename It::iterator_category; -}; - -// Detect if *any* given type models the OutputIterator concept. -template class is_output_iterator { - // Check for mutability because all iterator categories derived from - // std::input_iterator_tag *may* also meet the requirements of an - // OutputIterator, thereby falling into the category of 'mutable iterators' - // [iterator.requirements.general] clause 4. The compiler reveals this - // property only at the point of *actually dereferencing* the iterator! - template - static decltype(*(std::declval())) test(std::input_iterator_tag); - template static char& test(std::output_iterator_tag); - template static const char& test(...); - - using type = decltype(test(typename iterator_category::type{})); - - public: - enum { value = !std::is_const>::value }; -}; - // A workaround for std::string not having mutable data() until C++17. -template inline Char* get_data(std::basic_string& s) { +template +inline auto get_data(std::basic_string& s) -> Char* { return &s[0]; } template -inline typename Container::value_type* get_data(Container& c) { +inline auto get_data(Container& c) -> typename Container::value_type* { return c.data(); } -#if defined(_SECURE_SCL) && _SECURE_SCL -// Make a checked iterator to avoid MSVC warnings. -template using checked_ptr = stdext::checked_array_iterator; -template checked_ptr make_checked(T* p, size_t size) { - return {p, size}; -} -#else -template using checked_ptr = T*; -template inline T* make_checked(T* p, size_t) { return p; } -#endif - -template ::value)> -#if FMT_CLANG_VERSION +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. +template ::value&& + is_contiguous::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif -inline checked_ptr -reserve(std::back_insert_iterator it, size_t n) { - Container& c = get_container(it); +inline auto +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); - return make_checked(get_data(c) + size, n); + return get_data(c) + size; } -template inline Iterator& reserve(Iterator& it, size_t) { - return it; -} - -template ::value)> -inline std::back_insert_iterator base_iterator( - std::back_insert_iterator& it, - checked_ptr) { +template +inline auto reserve(basic_appender it, size_t n) -> basic_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); return it; } template -inline Iterator base_iterator(Iterator, Iterator it) { +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { return it; } -// An output iterator that counts the number of objects written to it and -// discards them. -class counting_iterator { - private: - size_t count_; - - public: - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - using _Unchecked_type = counting_iterator; // Mark iterator as checked. - - struct value_type { - template void operator=(const T&) {} - }; - - counting_iterator() : count_(0) {} - - size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; - } - - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type operator*() const { return {}; } -}; - -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_; - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit), count_(0) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = void; - using pointer = void; - using reference = void; - using _Unchecked_type = - truncating_iterator_base; // Mark iterator as checked. - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; +using reserve_iterator = + remove_reference_t(), 0))>; - public: - using value_type = typename truncating_iterator_base::value_type; +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template auto to_pointer(basic_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + auto size = buf.size(); + buf.try_reserve(size + n); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { + return it; +} - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) { + return fill_n(out, count, value); } + std::memset(out, value, to_unsigned(count)); + return out + count; +} - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); +} -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr const int shiftc[] = {0, 18, 12, 6, 0}; + constexpr const int shifte[] = {0, 6, 4, 2, 0}; + + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* buf_ptr, const char* ptr) { + auto cp = uint32_t(); + auto error = 0; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; + }; + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; + if (auto num_chars_left = s.data() + s.size() - p) { + char buf[2 * block_size - 1] = {}; + copy(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr - buf < num_chars_left); + } +} template -inline size_t count_code_points(basic_string_view s) { +inline auto compute_width(basic_string_view s) -> size_t { return s.size(); } -// Counts the number of code points in a UTF-8 string. -inline size_t count_code_points(basic_string_view s) { - const char* data = s.data(); +// Computes approximate display width of a UTF-8 string. +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80) ++num_code_points; - } + // It is not a lambda for compatibility with C++14. + struct count_code_points { + size_t* count; + FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { + *count += detail::to_unsigned( + 1 + + (cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); + return true; + } + }; + // We could avoid branches by using utf8_decode directly. + for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } -inline size_t count_code_points(basic_string_view s) { - return count_code_points(basic_string_view( - reinterpret_cast(s.data()), s.size())); -} - template -inline size_t code_point_index(basic_string_view s, size_t n) { +inline auto code_point_index(basic_string_view s, size_t n) -> size_t { size_t size = s.size(); return n < size ? n : size; } // Calculates the index of the nth code point in a UTF-8 string. -inline size_t code_point_index(basic_string_view s, size_t n) { - const char8_type* data = s.data(); - size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) { - return i; +inline auto code_point_index(string_view s, size_t n) -> size_t { + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; } - } - return s.size(); + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; } -template -using needs_conversion = bool_constant< - std::is_same::value_type, - char>::value && - std::is_same::value>; +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; -template ::value)> -OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::copy(begin, end, it); -} +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; -template ::value)> -OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::transform(begin, end, it, - [](char c) { return static_cast(c); }); -} +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; -#ifndef FMT_USE_GRISU -# define FMT_USE_GRISU 1 +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 #endif -template constexpr bool use_grisu() { - return FMT_USE_GRISU && std::numeric_limits::is_iec559 && - sizeof(T) <= sizeof(double); -} +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +using float128 = void; +#endif -template -template -void buffer::append(const U* begin, const U* end) { - size_t new_size = size_ + to_unsigned(end - begin); - reserve(new_size); - std::uninitialized_copy(begin, end, - make_checked(ptr_ + size_, capacity_ - size_)); - size_ = new_size; -} -} // namespace detail +template using is_float128 = std::is_same; -// The number of characters to store in the basic_memory_buffer object itself -// to avoid dynamic memory allocation. -enum { inline_buffer_size = 500 }; +template +using is_floating_point = + bool_constant::value || is_float128::value>; -/** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; - You can use one of the following type aliases for common character types: +template +using is_double_double = bool_constant::digits == 106>; - +----------------+------------------------------+ - | Type | Definition | - +================+==============================+ - | memory_buffer | basic_memory_buffer | - +----------------+------------------------------+ - | wmemory_buffer | basic_memory_buffer | - +----------------+------------------------------+ +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif - **Example**:: +template +struct is_locale : std::false_type {}; +template +struct is_locale> : std::true_type {}; +} // namespace detail - fmt::memory_buffer out; - format_to(out, "The answer is {}.", 42); +FMT_BEGIN_EXPORT - This will append the following output to the ``out`` object: +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst +/** + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. */ template > @@ -614,62 +836,80 @@ class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; - // Don't inherit from Allocator avoid generating type_info for it. - Allocator alloc_; + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; // Deallocate memory allocated by the buffer. - void deallocate() { + FMT_CONSTEXPR20 void deallocate() { T* data = this->data(); if (data != store_) alloc_.deallocate(data, this->capacity()); } - protected: - void grow(size_t size) FMT_OVERRIDE; + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { + detail::abort_fuzzing_if(size > 5000); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(buf.size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); + } public: using value_type = T; using const_reference = const T&; - explicit basic_memory_buffer(const Allocator& alloc = Allocator()) - : alloc_(alloc) { + FMT_CONSTEXPR20 explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) + : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); } - ~basic_memory_buffer() FMT_OVERRIDE { deallocate(); } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } private: // Move data from other to this buffer. - void move(basic_memory_buffer& other) { + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { alloc_ = std::move(other.alloc_); T* data = other.data(); size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - std::uninitialized_copy(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); + detail::copy(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. other.set(other.store_, 0); + other.clear(); } this->resize(size); } public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); } - - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ - basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT { + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { + move(other); + } + + /// Moves the content of the other `basic_memory_buffer` object to this one. + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); move(other); @@ -677,122 +917,220 @@ class basic_memory_buffer : public detail::buffer { } // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return alloc_; } + auto get_allocator() const -> Allocator { return alloc_; } + + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. + FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } + + /// Increases the buffer capacity to `new_capacity`. + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + using detail::buffer::append; + template + void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } }; +using memory_buffer = basic_memory_buffer; + template -void basic_memory_buffer::grow(size_t size) { -#ifdef FMT_FUZZ - if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); +struct is_contiguous> : std::true_type { +}; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(std::FILE*, string_view); +} // namespace detail + +FMT_BEGIN_EXPORT + +// Suppress a misleading warning in older versions of clang. +#if FMT_CLANG_VERSION +# pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +namespace detail_exported { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template struct fixed_string { + constexpr fixed_string(const Char (&str)[N]) { + detail::copy(static_cast(str), + str + N, data); + } + Char data[N] = {}; +}; #endif - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) new_capacity = size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); + +// Converts a compile-time string to basic_string_view. +template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } +template +constexpr auto compile_string_to_view(basic_string_view s) + -> basic_string_view { + return s; +} +} // namespace detail_exported -using memory_buffer = basic_memory_buffer; -using wmemory_buffer = basic_memory_buffer; +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; -template -struct is_contiguous> : std::true_type { + public: + using char_type = Char; + using iterator = OutputIt; + using parse_context_type = basic_format_parse_context; + template using formatter_type = formatter; + + constexpr generic_context(OutputIt out, + basic_format_args ctx_args, + detail::locale_ref loc = {}) + : out_(out), args_(ctx_args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) -> basic_format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args& { + return args_; + } + + FMT_CONSTEXPR auto out() -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } }; -/** A formatting error such as invalid format string. */ -FMT_CLASS_API -class FMT_API format_error : public std::runtime_error { +class loc_value { + private: + basic_format_arg value_; + public: - explicit format_error(const char* message) : std::runtime_error(message) {} - explicit format_error(const std::string& message) - : std::runtime_error(message) {} - format_error(const format_error&) = default; - format_error& operator=(const format_error&) = default; - format_error(format_error&&) = default; - format_error& operator=(format_error&&) = default; - ~format_error() FMT_NOEXCEPT FMT_OVERRIDE; + template ::value)> + loc_value(T value) : value_(detail::make_arg(value)) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } }; -namespace detail { +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", + std::initializer_list g = {3}, + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(g.begin(), g.end()), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +FMT_END_EXPORT + +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> -FMT_CONSTEXPR bool is_negative(T value) { +constexpr auto is_negative(T value) -> bool { return value < 0; } template ::value)> -FMT_CONSTEXPR bool is_negative(T) { +constexpr auto is_negative(T) -> bool { return false; } -template ::value)> -FMT_CONSTEXPR bool is_supported_floating_point(T) { - return (std::is_same::value && FMT_USE_FLOAT) || - (std::is_same::value && FMT_USE_DOUBLE) || - (std::is_same::value && FMT_USE_LONG_DOUBLE); +template +FMT_CONSTEXPR auto is_supported_floating_point(T) -> bool { + if (std::is_same()) return FMT_USE_FLOAT; + if (std::is_same()) return FMT_USE_DOUBLE; + if (std::is_same()) return FMT_USE_LONG_DOUBLE; + return true; } // Smallest of uint32_t, uint64_t, uint128_t that is large enough to -// represent all values of T. +// represent all values of an integral type T. template using uint32_or_64_or_128_t = - conditional_t() <= 32, uint32_t, + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 + +// Converts value in the range [0, 100) to a string. +constexpr auto digits2(size_t value) -> const char* { + // GCC generates slightly better code when value is pointer-size. + return &"0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"[value * 2]; +} -// Static data is placed in this class template for the header-only config. -template struct FMT_EXTERN_TEMPLATE_API basic_data { - static const uint64_t powers_of_10_64[]; - static const uint32_t zero_or_powers_of_10_32[]; - static const uint64_t zero_or_powers_of_10_64[]; - static const uint64_t pow10_significands[]; - static const int16_t pow10_exponents[]; - // GCC generates slightly better code for pairs than chars. - using digit_pair = char[2]; - static const digit_pair digits[]; - static const char hex_digits[]; - static const char foreground_color[]; - static const char background_color[]; - static const char reset_color[5]; - static const wchar_t wreset_color[5]; - static const char signs[]; - static const char left_padding_shifts[5]; - static const char right_padding_shifts[5]; -}; - -#ifndef FMT_EXPORTED -FMT_EXTERN template struct basic_data; +// Sign is a template parameter to workaround a bug in gcc 4.8. +template constexpr auto sign(Sign s) -> Char { +#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 + static_assert(std::is_same::value, ""); #endif - -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct data : basic_data<> {}; - -#ifdef FMT_BUILTIN_CLZLL -// Returns the number of decimal digits in n. Leading zeros are not counted -// except for n == 0 in which case count_digits returns 1. -inline int count_digits(uint64_t n) { - // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 - // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. - int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return t - (n < data::zero_or_powers_of_10_64[t]) + 1; + return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> (s * 8)); } -#else -// Fallback version of count_digits used when __builtin_clz is not available. -inline int count_digits(uint64_t n) { + +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { int count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead @@ -806,94 +1144,144 @@ inline int count_digits(uint64_t n) { count += 4; } } -#endif - #if FMT_USE_INT128 -inline int count_digits(uint128_t n) { - int count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000U; - count += 4; - } +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { + return count_digits_fallback(n); } #endif -// Counts the number of digits in n. BITS = log2(radix). -template inline int count_digits(UInt n) { - int num_digits = 0; - do { - ++num_digits; - } while ((n >>= BITS) != 0); - return num_digits; +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr const uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); } +#endif -template <> int count_digits<4>(detail::fallback_uintptr n); +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} -#if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) -#else -# define FMT_ALWAYS_INLINE +// Counts the number of digits in n. BITS = log2(radix). +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; #endif + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); +} #ifdef FMT_BUILTIN_CLZ -// Optional version of count_digits for better performance on 32-bit platforms. -inline int count_digits(uint32_t n) { - int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return t - (n < data::zero_or_powers_of_10_32[t]) + 1; +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) + static constexpr uint64_t table[] = { + FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 + FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); } #endif -template constexpr int digits10() FMT_NOEXCEPT { - return std::numeric_limits::digits10; +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) { + return do_count_digits(n); + } +#endif + return count_digits_fallback(n); } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template FMT_API std::string grouping_impl(locale_ref loc); -template inline std::string grouping(locale_ref loc) { - return grouping_impl(loc); -} -template <> inline std::string grouping(locale_ref loc) { - return grouping_impl(loc); +template constexpr auto digits10() noexcept -> int { + return std::numeric_limits::digits10; } +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } + +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; -template FMT_API Char thousands_sep_impl(locale_ref loc); -template inline Char thousands_sep(locale_ref loc) { - return Char(thousands_sep_impl(loc)); +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; } -template <> inline wchar_t thousands_sep(locale_ref loc) { +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { return thousands_sep_impl(loc); } -template FMT_API Char decimal_point_impl(locale_ref loc); -template inline Char decimal_point(locale_ref loc) { +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { return Char(decimal_point_impl(loc)); } -template <> inline wchar_t decimal_point(locale_ref loc) { +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } // Compares two characters for equality. -template bool equal2(const Char* lhs, const char* rhs) { - return lhs[0] == rhs[0] && lhs[1] == rhs[1]; +template auto equal2(const Char* lhs, const char* rhs) -> bool { + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); } -inline bool equal2(const char* lhs, const char* rhs) { +inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } // Copies two characters from src to dst. -template void copy2(Char* dst, const char* src) { +template +FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) { + if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) { + memcpy(dst, src, 2); + return; + } *dst++ = static_cast(*src++); *dst = static_cast(*src); } -inline void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } template struct format_decimal_result { Iterator begin; @@ -904,8 +1292,8 @@ template struct format_decimal_result { // buffer of specified size. The caller must ensure that the buffer is large // enough. template -inline format_decimal_result format_decimal(Char* out, UInt value, - int size) { +FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) + -> format_decimal_result { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); out += size; Char* end = out; @@ -914,7 +1302,7 @@ inline format_decimal_result format_decimal(Char* out, UInt value, // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. out -= 2; - copy2(out, data::digits[value % 100]); + copy2(out, digits2(static_cast(value % 100))); value /= 100; } if (value < 10) { @@ -922,172 +1310,277 @@ inline format_decimal_result format_decimal(Char* out, UInt value, return {out, end}; } out -= 2; - copy2(out, data::digits[value]); + copy2(out, digits2(static_cast(value))); return {out, end}; } template >::value)> -inline format_decimal_result format_decimal(Iterator out, UInt value, - int num_digits) { - // Buffer should be large enough to hold all digits (<= digits10 + 1). - enum { max_size = digits10() + 1 }; - Char buffer[2 * max_size]; - auto end = format_decimal(buffer, value, num_digits).end; - return {out, detail::copy_str(buffer, end, out)}; +FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) + -> format_decimal_result { + // Buffer is large enough to hold all digits (digits10 + 1). + Char buffer[digits10() + 1] = {}; + auto end = format_decimal(buffer, value, size).end; + return {out, detail::copy_noinline(buffer, end, out)}; } template -inline Char* format_uint(Char* buffer, UInt value, int num_digits, - bool upper = false) { +FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, + bool upper = false) -> Char* { buffer += num_digits; Char* end = buffer; do { - const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits; - unsigned digit = (value & ((1 << BASE_BITS) - 1)); + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1 << BASE_BITS) - 1)); *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= BASE_BITS) != 0); return end; } -template -Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, - bool = false) { - auto char_digits = std::numeric_limits::digits / 4; - int start = (num_digits + char_digits - 1) / char_digits - 1; - if (int start_digits = num_digits % char_digits) { - unsigned value = n.value[start--]; - buffer = format_uint(buffer, value, start_digits); - } - for (; start >= 0; --start) { - unsigned value = n.value[start]; - buffer += char_digits; - auto p = buffer; - for (int i = 0; i < char_digits; ++i) { - unsigned digit = (value & ((1 << BASE_BITS) - 1)); - *--p = static_cast(data::hex_digits[digit]); - value >>= BASE_BITS; - } - } - return buffer; -} - template -inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { +FMT_CONSTEXPR inline auto format_uint(It out, UInt value, int num_digits, + bool upper = false) -> It { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_uint(ptr, value, num_digits, upper); + return out; + } // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). - char buffer[num_bits() / BASE_BITS + 1]; + char buffer[num_bits() / BASE_BITS + 1] = {}; format_uint(buffer, value, num_digits, upper); - return detail::copy_str(buffer, buffer + num_digits, out); + return detail::copy_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. class utf8_to_utf16 { private: - wmemory_buffer buffer_; + basic_memory_buffer buffer_; public: FMT_API explicit utf8_to_utf16(string_view s); - operator wstring_view() const { return {&buffer_[0], size()}; } - size_t size() const { return buffer_.size() - 1; } - const wchar_t* c_str() const { return &buffer_[0]; } - std::wstring str() const { return {&buffer_[0], size()}; } + operator basic_string_view() const { return {&buffer_[0], size()}; } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const wchar_t* { return &buffer_[0]; } + auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; -template struct null {}; +enum class to_utf8_error_policy { abort, replace }; -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template class to_utf8 { private: - enum { max_size = 4 }; - Char data_[max_size]; - unsigned char size_; + Buffer buffer_; public: - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - if (size > max_size) { - FMT_THROW(format_error("invalid fill")); - return; + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "Expect utf16 or utf32"); + if (!convert(s, policy)) + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + operator string_view() const { return string_view(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; + buffer_.push_back(0); + return true; + } + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + } else { + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + } else if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } } - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; - size_ = static_cast(size); + return true; } +}; - size_t size() const { return size_; } - const Char* data() const { return data_; } +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast(max_value()); - FMT_CONSTEXPR Char& operator[](size_t index) { return data_[index]; } - FMT_CONSTEXPR const Char& operator[](size_t index) const { - return data_[index]; - } + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; - static FMT_CONSTEXPR fill_t make() { - auto fill = fill_t(); - fill[0] = Char(' '); - fill.size_ = 1; - return fill; - } -}; -} // namespace detail + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} -// We cannot use enum classes as bit fields because of a gcc bug -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. -namespace align { -enum type { none, left, right, center, numeric }; +namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline auto floor_log10_pow2(int e) noexcept -> int { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; } -using align_t = align::type; -namespace sign { -enum type { none, minus, plus, space }; +inline auto floor_log2_pow10(int e) noexcept -> int { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; } -using sign_t = sign::type; -// Format specifiers for built-in and string types. -template struct basic_format_specs { - int width; - int precision; - char type; - align_t align : 4; - sign_t sign : 3; - bool alt : 1; // Alternate form ('#'). - detail::fill_t fill; - - constexpr basic_format_specs() - : width(0), - precision(-1), - type(0), - align(align::none), - sign(sign::none), - alt(false), - fill(detail::fill_t::make()) {} +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; }; -using format_specs = basic_format_specs; +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 341; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; +}; -namespace detail { +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; }; -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool use_grisu : 1; - bool showpoint : 1; +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; }; +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; +} // namespace dragonbox + +// Returns true iff Float has the implicit bit which is not stored. +template constexpr auto has_implicit_bit() -> bool { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr auto num_significand_bits() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; +} + // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. -template It write_exponent(int exp, It it) { +template +FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { *it++ = static_cast('-'); @@ -1096,333 +1589,346 @@ template It write_exponent(int exp, It it) { *it++ = static_cast('+'); } if (exp >= 100) { - const char* top = data::digits[exp / 100]; + const char* top = digits2(to_unsigned(exp / 100)); if (exp >= 1000) *it++ = static_cast(top[0]); *it++ = static_cast(top[1]); exp %= 100; } - const char* d = data::digits[exp]; + const char* d = digits2(to_unsigned(exp)); *it++ = static_cast(d[0]); *it++ = static_cast(d[1]); return it; } -template class float_writer { - private: - // The number is given as v = digits_ * pow(10, exp_). - const char* digits_; - int num_digits_; - int exp_; - size_t size_; - float_specs specs_; - Char decimal_point_; - - template It prettify(It it) const { - // pow(10, full_exp - 1) <= v <= pow(10, full_exp). - int full_exp = num_digits_ + exp_; - if (specs_.format == float_format::exp) { - // Insert a decimal point after the first digit and add an exponent. - *it++ = static_cast(*digits_); - int num_zeros = specs_.precision - num_digits_; - if (num_digits_ > 1 || specs_.showpoint) *it++ = decimal_point_; - it = copy_str(digits_ + 1, digits_ + num_digits_, it); - if (num_zeros > 0 && specs_.showpoint) - it = std::fill_n(it, num_zeros, static_cast('0')); - *it++ = static_cast(specs_.upper ? 'E' : 'e'); - return write_exponent(full_exp - 1, it); - } - if (num_digits_ <= full_exp) { - // 1234e7 -> 12340000000[.0+] - it = copy_str(digits_, digits_ + num_digits_, it); - it = std::fill_n(it, full_exp - num_digits_, static_cast('0')); - if (specs_.showpoint || specs_.precision < 0) { - *it++ = decimal_point_; - int num_zeros = specs_.precision - full_exp; - if (num_zeros <= 0) { - if (specs_.format != float_format::fixed) - *it++ = static_cast('0'); - return it; - } -#ifdef FMT_FUZZ - if (num_zeros > 5000) - throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); -#endif - it = std::fill_n(it, num_zeros, static_cast('0')); - } - } else if (full_exp > 0) { - // 1234e-2 -> 12.34[0+] - it = copy_str(digits_, digits_ + full_exp, it); - if (!specs_.showpoint) { - // Remove trailing zeros. - int num_digits = num_digits_; - while (num_digits > full_exp && digits_[num_digits - 1] == '0') - --num_digits; - if (num_digits != full_exp) *it++ = decimal_point_; - return copy_str(digits_ + full_exp, digits_ + num_digits, it); - } - *it++ = decimal_point_; - it = copy_str(digits_ + full_exp, digits_ + num_digits_, it); - if (specs_.precision > num_digits_) { - // Add trailing zeros. - int num_zeros = specs_.precision - num_digits_; - it = std::fill_n(it, num_zeros, static_cast('0')); - } - } else { - // 1234e-6 -> 0.001234 - *it++ = static_cast('0'); - int num_zeros = -full_exp; - int num_digits = num_digits_; - if (num_digits == 0 && specs_.precision >= 0 && - specs_.precision < num_zeros) { - num_zeros = specs_.precision; - } - // Remove trailing zeros. - if (!specs_.showpoint) - while (num_digits > 0 && digits_[num_digits - 1] == '0') --num_digits; - if (num_zeros != 0 || num_digits != 0 || specs_.showpoint) { - *it++ = decimal_point_; - it = std::fill_n(it, num_zeros, static_cast('0')); - it = copy_str(digits_, digits_ + num_digits, it); - } - } - return it; - } - - public: - float_writer(const char* digits, int num_digits, int exp, float_specs specs, - Char decimal_point) - : digits_(digits), - num_digits_(num_digits), - exp_(exp), - specs_(specs), - decimal_point_(decimal_point) { - int full_exp = num_digits + exp - 1; - int precision = specs.precision > 0 ? specs.precision : 16; - if (specs_.format == float_format::general && - !(full_exp >= -4 && full_exp < precision)) { - specs_.format = float_format::exp; - } - size_ = prettify(counting_iterator()).count(); - size_ += specs.sign ? 1 : 0; - } - - size_t size() const { return size_; } - - template It operator()(It it) const { - if (specs_.sign) *it++ = static_cast(data::signs[specs_.sign]); - return prettify(it); +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr const int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } + + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); } }; -template -int format_float(T value, int precision, float_specs specs, buffer& buf); - -// Formats a floating-point number with snprintf. -template -int snprintf_float(T value, int precision, float_specs specs, - buffer& buf); - -template T promote_float(T value) { return value; } -inline double promote_float(float value) { return static_cast(value); } - -template -FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { - switch (spec) { - case 0: - case 'd': - handler.on_dec(); - break; - case 'x': - case 'X': - handler.on_hex(); - break; - case 'b': - case 'B': - handler.on_bin(); - break; - case 'o': - handler.on_oct(); - break; -#ifdef FMT_DEPRECATED_N_SPECIFIER - case 'n': -#endif - case 'L': - handler.on_num(); - break; - case 'c': - handler.on_chr(); - break; - default: - handler.on_error(); - } +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; } -template -FMT_CONSTEXPR float_specs parse_float_type_spec( - const basic_format_specs& specs, ErrorHandler&& eh = {}) { - auto result = float_specs(); - result.showpoint = specs.alt; - switch (specs.type) { - case 0: - result.format = float_format::general; - result.showpoint |= specs.precision > 0; - break; - case 'G': - result.upper = true; - FMT_FALLTHROUGH; - case 'g': - result.format = float_format::general; - break; - case 'E': - result.upper = true; - FMT_FALLTHROUGH; - case 'e': - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case 'F': - result.upper = true; - FMT_FALLTHROUGH; - case 'f': - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case 'A': - result.upper = true; - FMT_FALLTHROUGH; - case 'a': - result.format = float_format::hex; - break; -#ifdef FMT_DEPRECATED_N_SPECIFIER - case 'n': +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); #endif - case 'L': - result.locale = true; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; } -template -FMT_CONSTEXPR void handle_char_specs(const basic_format_specs* specs, - Handler&& handler) { - if (!specs) return handler.on_char(); - if (specs->type && specs->type != 'c') return handler.on_int(); - if (specs->align == align::numeric || specs->sign != sign::none || specs->alt) - handler.on_error("invalid format specifier for char"); - handler.on_char(); +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { + return {multiply(x.f, y.f), x.e + y.e + 64}; } -template -FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler&& handler) { - if (spec == 0 || spec == 's') - handler.on_string(); - else if (spec == 'p') - handler.on_pointer(); - else - handler.on_error("invalid type specifier"); -} +template () == num_bits()> +using convert_float_result = + conditional_t::value || doublish, double, T>; -template -FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh) { - if (spec != 0 && spec != 's') eh.on_error("invalid type specifier"); +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); } -template -FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) { - if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier"); -} - -template class int_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_dec() {} - FMT_CONSTEXPR void on_hex() {} - FMT_CONSTEXPR void on_bin() {} - FMT_CONSTEXPR void on_oct() {} - FMT_CONSTEXPR void on_num() {} - FMT_CONSTEXPR void on_chr() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template -class char_specs_checker : public ErrorHandler { - private: - char type_; - - public: - FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh) - : ErrorHandler(eh), type_(type) {} - - FMT_CONSTEXPR void on_int() { - handle_int_type_spec(type_, int_type_checker(*this)); - } - FMT_CONSTEXPR void on_char() {} -}; - -template -class cstring_type_checker : public ErrorHandler { - public: - FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_string() {} - FMT_CONSTEXPR void on_pointer() {} -}; - -template -FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { - auto fill_size = fill.size(); - if (fill_size == 1) return std::fill_n(it, n, fill[0]); - for (size_t i = 0; i < n; ++i) it = std::copy_n(fill.data(), fill_size, it); - return it; +template +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) + -> OutputIt { + auto fill_size = fill.size(); + if (fill_size == 1) return detail::fill_n(it, n, fill.template get()); + if (const Char* data = fill.template data()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } + return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. -template -inline OutputIt write_padded(OutputIt out, - const basic_format_specs& specs, size_t size, - size_t width, const F& f) { +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; - auto* shifts = align == align::left ? data::left_padding_shifts - : data::right_padding_shifts; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; size_t left_padding = padding >> shifts[specs.align]; + size_t right_padding = padding - left_padding; auto it = reserve(out, size + padding * specs.fill.size()); - it = fill(it, left_padding, specs.fill); + if (left_padding != 0) it = fill(it, left_padding, specs.fill); it = f(it); - it = fill(it, padding - left_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs.fill); return base_iterator(out, it); } -template -inline OutputIt write_padded(OutputIt out, - const basic_format_specs& specs, size_t size, - const F& f) { - return write_padded(out, specs, size, size, f); +constexpr auto write_padded(OutputIt out, const format_specs& specs, + size_t size, F&& f) -> OutputIt { + return write_padded(out, specs, size, size, f); +} + +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const format_specs& specs = {}) -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, Char>(it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || + !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (!use_utf8()) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +#define FMT_STRING_IMPL(s, base, explicit) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ + FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ + operator fmt::basic_string_view() const { \ + return fmt::detail_exported::compile_string_to_view(s); \ + } \ + }; \ + return FMT_COMPILE_STRING(); \ + }() + +/** + * Constructs a compile-time format string from a string literal `s`. + * + * **Example**: + * + * // A compile-time error because 'd' is an invalid specifier for strings. + * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string, ) + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_uint<4>(buf, cp, width); + return copy(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': + FMT_FALLTHROUGH; + case '\'': + FMT_FALLTHROUGH; + case '\\': + *out++ = static_cast('\\'); + break; + default: + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) + return write_codepoint<4, Char>(out, 'u', escape.cp); + if (escape.cp < 0x110000) + return write_codepoint<8, Char>(out, 'U', escape.cp); + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + +template +auto write_escaped_string(OutputIt out, basic_string_view str) + -> OutputIt { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; } template -OutputIt write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, bytes.size(), [bytes](iterator it) { - const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); + *it++ = value; + return it; }); } +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, + locale_ref loc = {}) -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} // Data for write_int that doesn't depend on output iterator type. It is used to // avoid template code bloat. @@ -1430,9 +1936,9 @@ template struct write_int_data { size_t size; size_t padding; - write_int_data(int num_digits, string_view prefix, - const basic_format_specs& specs) - : size(prefix.size() + to_unsigned(num_digits)), padding(0) { + FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, + const format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { @@ -1440,7 +1946,7 @@ template struct write_int_data { size = width; } } else if (specs.precision > num_digits) { - size = prefix.size() + to_unsigned(specs.precision); + size = (prefix >> 24) + to_unsigned(specs.precision); padding = to_unsigned(specs.precision - num_digits); } } @@ -1448,1577 +1954,1966 @@ template struct write_int_data { // Writes an integer in the format // -// where are written by f(it). -template -OutputIt write_int(OutputIt out, int num_digits, string_view prefix, - const basic_format_specs& specs, F f) { - auto data = write_int_data(num_digits, prefix, specs); - using iterator = remove_reference_t; - return write_padded(out, specs, data.size, [=](iterator it) { - if (prefix.size() != 0) - it = copy_str(prefix.begin(), prefix.end(), it); - it = std::fill_n(it, data.padding, static_cast('0')); - return f(it); - }); -} - -template -OutputIt write(OutputIt out, basic_string_view s, - const basic_format_specs& specs) { - auto data = s.data(); - auto size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - auto width = specs.width != 0 - ? count_code_points(basic_string_view(data, size)) - : 0; - using iterator = remove_reference_t; - return write_padded(out, specs, size, width, [=](iterator it) { - return copy_str(data, data + size, it); - }); -} - -// The handle_int_type_spec handler that writes an integer. -template struct int_writer { - OutputIt out; - locale_ref locale; - const basic_format_specs& specs; - UInt abs_value; - char prefix[4]; - unsigned prefix_size; - - using iterator = - remove_reference_t(), 0))>; - - string_view get_prefix() const { return string_view(prefix, prefix_size); } - - template - int_writer(OutputIt output, locale_ref loc, Int value, - const basic_format_specs& s) - : out(output), - locale(loc), - specs(s), - abs_value(static_cast(value)), - prefix_size(0) { - static_assert(std::is_same, UInt>::value, ""); - if (is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (specs.sign != sign::none && specs.sign != sign::minus) { - prefix[0] = specs.sign == sign::plus ? '+' : ' '; - ++prefix_size; +// where are written by write_digits(it). +// prefix contains chars in three lower bytes and the size in the fourth byte. +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, + unsigned prefix, + const format_specs& specs, + W write_digits) -> OutputIt { + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + if (prefix != 0) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); } + return base_iterator(out, write_digits(it)); } + auto data = write_int_data(num_digits, prefix, specs); + return write_padded( + out, specs, data.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, data.padding, static_cast('0')); + return write_digits(it); + }); +} - void on_dec() { - auto num_digits = count_digits(abs_value); - out = write_int( - out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { - return format_decimal(it, abs_value, num_digits).end; - }); - } - - void on_hex() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = specs.type; - } - int num_digits = count_digits<4>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, - specs.type != 'x'); - }); - } +template class digit_grouping { + private: + std::string grouping_; + std::basic_string thousands_sep_; - void on_bin() { - if (specs.alt) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast(specs.type); - } - int num_digits = count_digits<1>(abs_value); - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); - } + struct next_state { + std::string::const_iterator group; + int pos; + }; + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } - void on_oct() { - int num_digits = count_digits<3>(abs_value); - if (specs.alt && specs.precision <= num_digits && abs_value != 0) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - out = write_int(out, num_digits, get_prefix(), specs, - [this, num_digits](iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); + // Returns the next digit group separator position. + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; } - enum { sep_size = 1 }; - - void on_num() { - std::string groups = grouping(locale); - if (groups.empty()) return on_dec(); - auto sep = thousands_sep(locale); - if (!sep) return on_dec(); - int num_digits = count_digits(abs_value); - int size = num_digits, n = num_digits; - std::string::const_iterator group = groups.cbegin(); - while (group != groups.cend() && n > *group && *group > 0 && - *group != max_value()) { - size += sep_size; - n -= *group; - ++group; + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + } + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + + auto has_separator() const -> bool { return !thousands_sep_.empty(); } + + auto count_separators(int num_digits) const -> int { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + auto apply(Out out, basic_string_view digits) const -> Out { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); } - if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); - char digits[40]; - format_decimal(digits, abs_value, num_digits); - basic_memory_buffer buffer; - size += prefix_size; - buffer.resize(size); - basic_string_view s(&sep, sep_size); - // Index of a decimal digit with the least significant digit having index 0. - int digit_index = 0; - group = groups.cbegin(); - auto p = buffer.data() + size; - for (int i = num_digits - 1; i >= 0; --i) { - *--p = static_cast(digits[i]); - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - continue; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); + --sep_index; } - p -= s.size(); - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(p, s.size())); + *out++ = static_cast(digits[to_unsigned(i)]); } - if (prefix_size != 0) p[-1] = static_cast('-'); - using iterator_t = remove_reference_t; - auto data = buffer.data(); - out = write_padded(out, specs, size, size, [=](iterator_t it) { - return copy_str(data, data + size, it); - }); - } - - void on_chr() { *out++ = static_cast(abs_value); } - - FMT_NORETURN void on_error() { - FMT_THROW(format_error("invalid type specifier")); + return out; } }; -template -OutputIt write_nonfinite(OutputIt out, bool isinf, - const basic_format_specs& specs, - const float_specs& fspecs) { - auto str = - isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); - constexpr size_t str_size = 3; - auto sign = fspecs.sign; - auto size = str_size + (sign ? 1 : 0); - using iterator = remove_reference_t; - return write_padded(out, specs, size, [=](iterator it) { - if (sign) *it++ = static_cast(data::signs[sign]); - return copy_str(str, str + str_size, it); - }); -} - -template ::value)> -OutputIt write(OutputIt out, T value, basic_format_specs specs, - locale_ref loc = {}) { - if (const_check(!is_supported_floating_point(value))) return out; - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } - - if (!std::isfinite(value)) - return write_nonfinite(out, std::isinf(value), specs, fspecs); - - if (specs.align == align::numeric && fspecs.sign) { - auto it = reserve(out, 1); - *it++ = static_cast(data::signs[fspecs.sign]); - out = base_iterator(out, it); - fspecs.sign = sign::none; - if (specs.width != 0) --specs.width; - } - - memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); - snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - return write_bytes(out, {buffer.data(), buffer.size()}, specs); - } - int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; - if (fspecs.format == float_format::exp) { - if (precision == max_value()) - FMT_THROW(format_error("number is too big")); - else - ++precision; - } - if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); - int exp = format_float(promote_float(value), precision, fspecs, buffer); - fspecs.precision = precision; - Char point = - fspecs.locale ? decimal_point(loc) : static_cast('.'); - float_writer w(buffer.data(), static_cast(buffer.size()), exp, - fspecs, point); - return write_padded(out, specs, w.size(), w); -} - -template ::value)> -OutputIt write(OutputIt out, T value) { - if (const_check(!is_supported_floating_point(value))) return out; - auto fspecs = float_specs(); - if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } - - auto specs = basic_format_specs(); - if (!std::isfinite(value)) - return write_nonfinite(out, std::isinf(value), specs, fspecs); - - memory_buffer buffer; - int precision = -1; - if (const_check(std::is_same())) fspecs.binary32 = true; - fspecs.use_grisu = use_grisu(); - int exp = format_float(promote_float(value), precision, fspecs, buffer); - fspecs.precision = precision; - float_writer w(buffer.data(), static_cast(buffer.size()), exp, - fspecs, static_cast('.')); - return base_iterator(out, w(reserve(out, w.size()))); -} - -template -OutputIt write_char(OutputIt out, Char value, - const basic_format_specs& specs) { - using iterator = remove_reference_t; - return write_padded(out, specs, 1, [=](iterator it) { - *it++ = value; - return it; - }); +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; } -template -OutputIt write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs* specs) { - int num_digits = count_digits<4>(value); - auto size = to_unsigned(num_digits) + size_t(2); - using iterator = remove_reference_t; - auto write = [=](iterator it) { - *it++ = static_cast('0'); - *it++ = static_cast('x'); - return format_uint<4, Char>(it, value, num_digits); - }; - return specs ? write_padded(out, *specs, size, write) - : base_iterator(out, write(reserve(out, size))); -} - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -OutputIt write(OutputIt out, monostate) { - FMT_ASSERT(false, ""); - return out; +// Writes a decimal integer with digit grouping. +template +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type) { + default: + FMT_ASSERT(false, ""); + FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_uint<4, char>(appender(buffer), value, num_digits, specs.upper); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_uint<3, char>(appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_uint<1, char>(appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); + }); } -template ::value)> -OutputIt write(OutputIt out, string_view value) { - auto it = reserve(out, value.size()); - it = copy_str(value.begin(), value.end(), it); - return base_iterator(out, it); +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +template +inline auto write_loc(OutputIt, loc_value, const format_specs&, locale_ref) + -> bool { + return false; } -template -OutputIt write(OutputIt out, basic_string_view value) { - auto it = reserve(out, value.size()); - it = std::copy(value.begin(), value.end(), it); - return base_iterator(out, it); -} +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; -template ::value && - !std::is_same::value && - !std::is_same::value)> -OutputIt write(OutputIt out, T value) { +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) + -> write_int_arg> { + auto prefix = 0u; auto abs_value = static_cast>(value); - bool negative = is_negative(value); - // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. - if (negative) abs_value = ~abs_value + 1; - int num_digits = count_digits(abs_value); - auto it = reserve(out, (negative ? 1 : 0) + static_cast(num_digits)); - if (negative) *it++ = static_cast('-'); - it = format_decimal(it, abs_value, num_digits).end; - return base_iterator(out, it); -} - -template -OutputIt write(OutputIt out, bool value) { - return write(out, string_view(value ? "true" : "false")); -} - -template -OutputIt write(OutputIt out, Char value) { - auto it = reserve(out, 1); - *it++ = value; - return base_iterator(out, it); -} - -template -OutputIt write(OutputIt out, const Char* value) { - if (!value) { - FMT_THROW(format_error("string pointer is null")); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; } else { - auto length = std::char_traits::length(value); - out = write(out, basic_string_view(value, length)); + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[sign]; } - return out; -} - -template -OutputIt write(OutputIt out, const void* value) { - return write_ptr(out, to_uintptr(value), nullptr); -} - -template -auto write(OutputIt out, const T& value) -> typename std::enable_if< - mapped_type_constant>::value == - type::custom_type, - OutputIt>::type { - basic_format_context ctx(out, {}, {}); - return formatter().format(value, ctx); + return {abs_value, prefix}; } -// An argument visitor that formats the argument and writes it via the output -// iterator. It's a class and not a generic lambda for compatibility with C++11. -template struct default_arg_formatter { - using context = basic_format_context; +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; - OutputIt out; - basic_format_args args; - locale_ref loc; - - template OutputIt operator()(T value) { - return write(out, value); + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; } - OutputIt operator()(typename basic_format_arg::handle handle) { - basic_format_parse_context parse_ctx({}); - basic_format_context format_ctx(out, args, loc); - handle.format(parse_ctx, format_ctx); - return format_ctx.out(); + template ::value)> + auto operator()(T) -> bool { + return false; } }; -template -class arg_formatter_base { - public: - using iterator = OutputIt; - using char_type = Char; - using format_specs = basic_format_specs; - - private: - iterator out_; - locale_ref locale_; - format_specs* specs_; - - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { - return detail::reserve(out_, n); - } - - using reserve_iterator = remove_reference_t(), 0))>; - - template void write_int(T value, const format_specs& spec) { - using uint_type = uint32_or_64_or_128_t; - int_writer w(out_, locale_, value, spec); - handle_int_type_spec(spec.type, w); - out_ = w.out; - } - - void write(char value) { - auto&& it = reserve(1); - *it++ = value; - } - - template ::value)> - void write(Ch value) { - out_ = detail::write(out_, value); - } - - void write(string_view value) { - auto&& it = reserve(value.size()); - it = copy_str(value.begin(), value.end(), it); +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const format_specs& specs, locale_ref) + -> OutputIt { + static_assert(std::is_same>::value, ""); + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type) { + default: + FMT_ASSERT(false, ""); + FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: { + int num_digits = count_digits(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_decimal(it, abs_value, num_digits).end; + }); } - void write(wstring_view value) { - static_assert(std::is_same::value, ""); - auto&& it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); + case presentation_type::hex: { + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); + int num_digits = count_digits<4>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, specs.upper); + }); } - - template - void write(const Ch* s, size_t size, const format_specs& specs) { - auto width = specs.width != 0 - ? count_code_points(basic_string_view(s, size)) - : 0; - out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) { - return copy_str(s, s + size, it); - }); + case presentation_type::oct: { + int num_digits = count_digits<3>(abs_value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); } - - template - void write(basic_string_view s, const format_specs& specs = {}) { - out_ = detail::write(out_, s, specs); + case presentation_type::bin: { + if (specs.alt) + prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0'); + int num_digits = count_digits<1>(abs_value); + return write_int( + out, num_digits, prefix, specs, [=](reserve_iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); } - - void write_pointer(const void* p) { - out_ = write_ptr(out_, to_uintptr(p), specs_); + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); } +} +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs, + locale_ref loc) -> OutputIt { + return write_int(out, arg, specs, loc); +} +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign), + specs, loc); +} +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign), specs, + loc); +} - struct char_spec_handler : ErrorHandler { - arg_formatter_base& formatter; - Char value; +// An output iterator that counts the number of objects written to it and +// discards them. +class counting_iterator { + private: + size_t count_; - char_spec_handler(arg_formatter_base& f, Char val) - : formatter(f), value(val) {} + public: + using iterator_category = std::output_iterator_tag; + using difference_type = std::ptrdiff_t; + using pointer = void; + using reference = void; + FMT_UNCHECKED_ITERATOR(counting_iterator); - void on_int() { - // char is only formatted as int if there are specs. - formatter.write_int(static_cast(value), *formatter.specs_); - } - void on_char() { - if (formatter.specs_) - formatter.out_ = write_char(formatter.out_, value, *formatter.specs_); - else - formatter.write(value); - } + struct value_type { + template FMT_CONSTEXPR void operator=(const T&) {} }; - struct cstring_spec_handler : error_handler { - arg_formatter_base& formatter; - const Char* value; - - cstring_spec_handler(arg_formatter_base& f, const Char* val) - : formatter(f), value(val) {} + FMT_CONSTEXPR counting_iterator() : count_(0) {} - void on_string() { formatter.write(value); } - void on_pointer() { formatter.write_pointer(value); } - }; - - protected: - iterator out() { return out_; } - format_specs* specs() { return specs_; } + FMT_CONSTEXPR auto count() const -> size_t { return count_; } - void write(bool value) { - if (specs_) - write(string_view(value ? "true" : "false"), *specs_); - else - out_ = detail::write(out_, value); + FMT_CONSTEXPR auto operator++() -> counting_iterator& { + ++count_; + return *this; } - - void write(const Char* value) { - if (!value) { - FMT_THROW(format_error("string pointer is null")); - } else { - auto length = std::char_traits::length(value); - basic_string_view sv(value, length); - specs_ ? write(sv, *specs_) : write(sv); - } + FMT_CONSTEXPR auto operator++(int) -> counting_iterator { + auto it = *this; + ++*this; + return it; } - public: - arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc) - : out_(out), locale_(loc), specs_(s) {} - - iterator operator()(monostate) { - FMT_ASSERT(false, "invalid argument type"); - return out_; + FMT_CONSTEXPR friend auto operator+(counting_iterator it, difference_type n) + -> counting_iterator { + it.count_ += static_cast(n); + return it; } - template ::value)> - FMT_INLINE iterator operator()(T value) { - if (specs_) - write_int(value, *specs_); - else - out_ = detail::write(out_, value); - return out_; - } + FMT_CONSTEXPR auto operator*() const -> value_type { return {}; } +}; - iterator operator()(Char value) { - handle_char_specs(specs_, - char_spec_handler(*this, static_cast(value))); - return out_; - } +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + bool is_debug = specs.type == presentation_type::debug; + size_t width = 0; - iterator operator()(bool value) { - if (specs_ && specs_->type) return (*this)(value ? 1 : 0); - write(value != 0); - return out_; - } + if (is_debug) size = write_escaped_string(counting_iterator{}, s).count(); - template ::value)> - iterator operator()(T value) { - auto specs = specs_ ? *specs_ : format_specs(); - if (const_check(is_supported_floating_point(value))) - out_ = detail::write(out_, value, specs, locale_); + if (specs.width != 0) { + if (is_debug) + width = size; else - FMT_ASSERT(false, "unsupported float argument type"); - return out_; - } - - iterator operator()(const Char* value) { - if (!specs_) return write(value), out_; - handle_cstring_type_spec(specs_->type, cstring_spec_handler(*this, value)); - return out_; - } - - iterator operator()(basic_string_view value) { - if (specs_) { - check_string_type_spec(specs_->type, error_handler()); - write(value, *specs_); - } else { - write(value); - } - return out_; + width = compute_width(basic_string_view(data, size)); } + return write_padded(out, specs, size, width, + [=](reserve_iterator it) { + if (is_debug) return write_escaped_string(it, s); + return copy(data, data + size, it); + }); +} +template +FMT_CONSTEXPR auto write(OutputIt out, + basic_string_view> s, + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); +} +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); +} - iterator operator()(const void* value) { - if (specs_) check_pointer_type_spec(specs_->type, error_handler()); - write_pointer(value); - return out_; +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + if (auto ptr = to_pointer(out, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; } -}; - -template FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; + if (negative) *out++ = static_cast('-'); + return format_decimal(out, abs_value, num_digits).end; } -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template -FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, - ErrorHandler&& eh) { - FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - constexpr unsigned max_int = max_value(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; +// DEPRECATED! +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; break; } - value = value * 10 + unsigned(*begin - '0'); - ++begin; - } while (begin != end && '0' <= *begin && *begin <= '9'); - if (value > max_int) eh.on_error("number is too big"); - return static_cast(value); -} - -template class custom_formatter { - private: - using char_type = typename Context::char_type; - - basic_format_parse_context& parse_ctx_; - Context& ctx_; - - public: - explicit custom_formatter(basic_format_parse_context& parse_ctx, - Context& ctx) - : parse_ctx_(parse_ctx), ctx_(ctx) {} - - bool operator()(typename basic_format_arg::handle h) const { - h.format(parse_ctx_, ctx_); - return true; + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + report_error("invalid fill character '{'"); + return begin; + } + specs.fill = basic_string_view(begin, to_unsigned(p - begin)); + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; } + specs.align = align; + return begin; +} - template bool operator()(T) const { return false; } +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed // Fixed point with the default precision of 6, e.g. 0.0012. }; -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - -template class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - - template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T value) { - if (is_negative(value)) handler_.on_error("negative width"); - return static_cast(value); - } - - template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T) { - handler_.on_error("width is not integer"); - return 0; - } - - private: - ErrorHandler& handler_; +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; }; -template class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - - template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T value) { - if (is_negative(value)) handler_.on_error("negative precision"); - return static_cast(value); +// DEPRECATED! +FMT_CONSTEXPR inline auto parse_float_type_spec(const format_specs& specs) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + default: + FMT_FALLTHROUGH; + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::exp: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::general: + result.format = float_format::general; + break; } + return result; +} - template ::value)> - FMT_CONSTEXPR unsigned long long operator()(T) { - handler_.on_error("precision is not integer"); - return 0; - } +template +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + format_specs specs, sign_t sign) + -> OutputIt { + auto str = + isnan ? (specs.upper ? "NAN" : "nan") : (specs.upper ? "INF" : "inf"); + constexpr size_t str_size = 3; + auto size = str_size + (sign ? 1 : 0); + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + specs.fill.size() == 1 && specs.fill.template get() == '0'; + if (is_zero_fill) specs.fill = ' '; + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (sign) *it++ = detail::sign(sign); + return copy(str, str + str_size, it); + }); +} - private: - ErrorHandler& handler_; +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; }; -// A format specifier handler that sets fields in basic_format_specs. -template class specs_setter { - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_plus() { specs_.sign = sign::plus; } - FMT_CONSTEXPR void on_minus() { specs_.sign = sign::minus; } - FMT_CONSTEXPR void on_space() { specs_.sign = sign::space; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); +} - FMT_CONSTEXPR void on_zero() { - specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } +template +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { + return copy(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size).end; +} +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; +template ::value)> +inline auto write_significand(Char* out, UInt significand, int significand_size, + int integral_size, Char decimal_point) -> Char* { + if (!decimal_point) + return format_decimal(out, significand, significand_size).end; + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + copy2(out, digits2(static_cast(significand % 100))); + significand /= 100; } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(Char type) { - specs_.type = static_cast(type); + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); + return end; +} - protected: - basic_format_specs& specs_; -}; +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_noinline(buffer, end, out); +} -template class numeric_specs_checker { - public: - FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type) - : error_handler_(eh), arg_type_(arg_type) {} +template +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + out = detail::copy_noinline(significand, significand + integral_size, + out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); +} - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - error_handler_.on_error("format specifier requires numeric argument"); - } +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); +} - FMT_CONSTEXPR void check_sign() { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::char_type) { - error_handler_.on_error("format specifier requires signed argument"); +template > +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, + float_specs fspecs, locale_ref loc) + -> OutputIt { + auto significand = f.significand; + int significand_size = get_significand_size(f); + const Char zero = static_cast('0'); + auto sign = fspecs.sign; + size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); + using iterator = reserve_iterator; + + Char decimal_point = + fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); + + int output_exp = f.exponent + significand_size - 1; + auto use_exp_format = [=]() { + if (fspecs.format == float_format::exp) return true; + if (fspecs.format != float_format::general) return false; + // Use the fixed notation if the exponent is in [exp_lower, exp_upper), + // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. + const int exp_lower = -4, exp_upper = 16; + return output_exp < exp_lower || + output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); + }; + if (use_exp_format()) { + int num_zeros = 0; + if (fspecs.showpoint) { + num_zeros = fspecs.precision - significand_size; + if (num_zeros < 0) num_zeros = 0; + size += to_unsigned(num_zeros); + } else if (significand_size == 1) { + decimal_point = Char(); } + auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; + int exp_digits = 2; + if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; + + size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); + char exp_char = specs.upper ? 'E' : 'e'; + auto write = [=](iterator it) { + if (sign) *it++ = detail::sign(sign); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + decimal_point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); + *it++ = static_cast(exp_char); + return write_exponent(output_exp, it); + }; + return specs.width > 0 + ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); } - FMT_CONSTEXPR void check_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - error_handler_.on_error("precision not allowed for this argument type"); + int exp = f.exponent + significand_size; + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += to_unsigned(f.exponent); + int num_zeros = fspecs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (fspecs.showpoint) { + ++size; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; + if (num_zeros > 0) size += to_unsigned(num_zeros); + } + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + it = write_significand(it, significand, significand_size, + f.exponent, grouping); + if (!fspecs.showpoint) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); + } else if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; + size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + auto grouping = Grouping(loc, fspecs.locale); + size += to_unsigned(grouping.count_separators(exp)); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + it = write_significand(it, significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; + }); } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && fspecs.precision >= 0 && + fspecs.precision < num_zeros) { + num_zeros = fspecs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; + size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); + return write_padded(out, specs, size, [&](iterator it) { + if (sign) *it++ = detail::sign(sign); + *it++ = zero; + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, zero); + return write_significand(it, significand, significand_size); + }); +} - private: - ErrorHandler& error_handler_; - detail::type arg_type_; -}; - -// A format specifier handler that checks if specifiers are consistent with the -// argument type. -template class specs_checker : public Handler { - private: - numeric_specs_checker checker_; - - // Suppress an MSVC warning about using this in initializer list. - FMT_CONSTEXPR Handler& error_handler() { return *this; } - +template class fallback_digit_grouping { public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), checker_(error_handler(), arg_type) {} - - FMT_CONSTEXPR specs_checker(const specs_checker& other) - : Handler(other), checker_(error_handler(), other.arg_type_) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) checker_.require_numeric_argument(); - Handler::on_align(align); - } + constexpr fallback_digit_grouping(locale_ref, bool) {} - FMT_CONSTEXPR void on_plus() { - checker_.check_sign(); - Handler::on_plus(); - } + constexpr auto has_separator() const -> bool { return false; } - FMT_CONSTEXPR void on_minus() { - checker_.check_sign(); - Handler::on_minus(); - } + constexpr auto count_separators(int) const -> int { return 0; } - FMT_CONSTEXPR void on_space() { - checker_.check_sign(); - Handler::on_space(); + template + constexpr auto apply(Out out, basic_string_view) const -> Out { + return out; } +}; - FMT_CONSTEXPR void on_hash() { - checker_.require_numeric_argument(); - Handler::on_hash(); +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, float_specs fspecs, + locale_ref loc) -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, f, specs, fspecs, + loc); + } else { + return do_write_float(out, f, specs, fspecs, loc); } +} - FMT_CONSTEXPR void on_zero() { - checker_.require_numeric_argument(); - Handler::on_zero(); - } +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. +} - FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } -}; +template +struct has_isfinite : std::false_type {}; -template