diff --git a/.clang-format b/.clang-format index 9cbc44b6..25eea8a6 100644 --- a/.clang-format +++ b/.clang-format @@ -2,18 +2,23 @@ AccessModifierOffset: -4 AlignConsecutiveAssignments: Consecutive AlignConsecutiveMacros: Consecutive +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: true AlignEscapedNewlines: Right AlignTrailingComments: Kind: Always -AllowShortBlocksOnASingleLine: Always +AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true -AllowShortEnumsOnASingleLine: true -AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: WithoutElse AllowShortLambdasOnASingleLine: All -AllowShortLoopsOnASingleLine: true +AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: Yes +AlwaysBreakTemplateDeclarations: MultiLine BasedOnStyle: LLVM BinPackArguments: false BinPackParameters: false @@ -31,40 +36,43 @@ BraceWrapping: BeforeElse: false BeforeLambdaBody: false BeforeWhile: false - IndentBraces: true - SplitEmptyFunction: true - SplitEmptyNamespace: true - SplitEmptyRecord: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyNamespace: false + SplitEmptyRecord: false +BracedInitializerIndentWidth: 4 BreakAfterAttributes: Never -BreakBeforeBraces: Allman +BreakBeforeBraces: Custom ColumnLimit: 160 CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: true Cpp11BracedListStyle: false -DerivePointerAlignment: true +DerivePointerAlignment: false FixNamespaceComments: true IncludeBlocks: Regroup IncludeCategories: # Headers in "" with .h extension. - - Regex: '"([A-Za-z0-9\/-_])+\.h"' + - Regex: '"[-A-Za-z0-9_/]+\.h"' Priority: 10 # Headers in "" with .hpp extension. - - Regex: '"([A-Za-z0-9\/-_])+\.hpp"' + - Regex: '"[-A-Za-z0-9_/]+\.hpp"' Priority: 20 # Headers in <> with .h extension. - - Regex: '<([A-Za-z0-9\/-_])+\.h>' + - Regex: '<[-A-Za-z0-9_/]+\.h>' Priority: 30 # Headers in <> with .hpp extension. - - Regex: '<([A-Za-z0-9\/-_])+\.hpp>' + - Regex: '<[-A-Za-z0-9_/]+\.hpp>' Priority: 40 # Headers in <> without an extension. - - Regex: '<([A-Za-z0-9\/-_])+>' + - Regex: '<[-A-Za-z0-9_/]+>' Priority: 50 IndentPPDirectives: BeforeHash IndentWidth: 4 +InsertNewlineAtEOF: true Language: Cpp NamespaceIndentation: All +PackConstructorInitializers: CurrentLine PointerAlignment: Middle +ReferenceAlignment: Middle SortIncludes: CaseInsensitive SpaceAfterTemplateKeyword: true SpaceBeforeParens: ControlStatements @@ -74,11 +82,10 @@ Standard: c++17 TabWidth: 4 UseTab: Always WhitespaceSensitiveMacros: - # Specific macros that suffer from formatting issues with white spaces + # Specific macros that suffer from formatting issues with white spaces. - CCM_DISABLE_CLANG_WARNING - CCM_ENABLE_CLANG_WARNING_AS_ERROR - CCM_DISABLE_GCC_WARNING - CCM_ENABLE_GCC_WARNING_AS_ERROR - CCM_ENABLE_IGNORE_GCC_WARNING - - CCM_DISABLE_IGNORE_GCC_WARNING -... + - CCM_DISABLE_IGNORE_GCC_WARNING \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy index 83214b57..428eaa7f 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,7 +7,8 @@ bugprone-*, modernize-*, -modernize-use-trailing-return-type, -modernize-use-constraints, --modernize-avoid-c-arrays, +-modernize-use-nodiscard, +-modernize-use-auto, performance-*, -performance-avoid-endl, @@ -15,12 +16,19 @@ performance-*, readability-*, -readability-identifier-length, -readability-magic-numbers, +-readability-function-cognitive-complexity, +-readability-uppercase-literal-suffix, cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-macro-usage, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-special-member-func, +-cppcoreguidelines-avoid-c-arrays, +-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access, +-cppcoreguidelines-pro-bounds-constant-array-index, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, cert-dcl21-cpp, cert-dcl58-cpp, @@ -41,6 +49,7 @@ llvm-namespace-comment, hicpp-exception-baseclass, hicpp-multiway-paths-covered, hicpp-no-assembler, +-hicpp-uppercase-literal-suffix, misc-const-correctness, misc-definitions-in-headers, misc-header-include-cycle, diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 116b710e..fb642b46 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,76 +1,205 @@ name: Bug Report -description: Create a report to help us improve +description: Report incorrect results, build failures, or other defects in ccmath title: '[BUG]: ' -labels: ['bug'] +labels: ['T: Bugfix', 'S: Untriaged'] body: - type: checkboxes attributes: - label: Due Diligence - description: Before you submit a bug report, please check the following + label: Due diligence options: - - label: I have searched for similar issues before starting this bug report + - label: I searched existing issues and did not find a duplicate required: true + - label: I reproduced this against the latest main (or a tagged release) + required: true + + - type: input + id: function + attributes: + label: Affected function(s) + description: 'The ccm:: function(s) involved.' + placeholder: ccm::pow, ccm::sin + validations: + required: true + + - type: dropdown + id: family + attributes: + label: Function family + options: + - basic + - compare + - expo + - fmanip + - hyper + - misc + - nearest + - power + - special + - trig + - not sure + validations: + required: true + + - type: dropdown + id: code-path + attributes: + label: Code path + description: Where the wrong result comes from. Select all that apply. + multiple: true + options: + - constexpr (compile-time) + - runtime (scalar kernel) + - SIMD (vectorized) + - deterministic mode (CCMATH_ENABLE_DETERMINISTIC) + - not sure + validations: + required: true + + - type: dropdown + id: scalar-type + attributes: + label: Scalar type + options: + - float + - double + - long double + - integral + - multiple / not applicable + validations: + required: true + - type: textarea id: description attributes: label: Describe the bug - description: A clear and concise description of what the bug is. - placeholder: e.g. ccm::sqrt(4) is incorrectly outputting 1.9999999... instead of 2.0 on clang-12 x86-64 + description: What is wrong, in one or two sentences. validations: required: true + + - type: textarea + id: input + attributes: + label: Input value(s) + description: Exact inputs. Prefer hex float so no precision is lost. + placeholder: 'x = 0x1.0000000000001p+0 (1.0000000000000002)' + validations: + required: true + + - type: input + id: expected + attributes: + label: Expected result (and source of truth) + placeholder: '0x1.5bf0a8b145769p+1, per MPFR / the C++ standard' + validations: + required: true + + - type: input + id: actual + attributes: + label: Actual result + placeholder: '0x1.5bf0a8b14576ap+1 (1 ULP high)' + validations: + required: true + + - type: input + id: error + attributes: + label: Error magnitude (ULP) if known + validations: + required: false + + - type: dropdown + id: rounding-mode + attributes: + label: Rounding mode + options: + - FE_TONEAREST (default) + - FE_DOWNWARD + - FE_UPWARD + - FE_TOWARDZERO + - unchanged / not sure + validations: + required: true + - type: textarea id: reproduction attributes: - label: Steps to reproduce - description: Steps used to reproduce the bug. + label: Minimal reproduction + description: A self-contained snippet. A Godbolt link is ideal. Print exact bits, not via raw std::cout. + render: cpp placeholder: | - 1. Set the compiler to clang-12 on x86-64 - 2. Write the following code: - ```cpp - #include // std::cout and std::endl + #include #include int main() { - std::cout << ccm::sqrt(4) << std::endl; + printf("%a\n", ccm::sqrt(4.0)); // expected 0x1p+1 } - ``` - 3. Execute the program - 4. See the output validations: required: true - - type: textarea - id: expected-behavior + + - type: markdown attributes: - label: Expected behavior - description: A clear and concise description of what you expected to happen. - placeholder: The output should be outputting 2.0 as this is the square root of 4, but it is outputting 1.9999999... instead. + value: '## Environment' + + - type: input + id: ccmath-version + attributes: + label: ccmath version or commit + placeholder: v0.2.0 or commit 65cb8ba9 validations: required: true - - type: markdown - attributes: - value: '# System information' + - type: input - id: compiler-version-and-type + id: compiler attributes: - label: Compiler version and type - description: The name of your compile and it's version - placeholder: GCC 13.2.1 (20230801) + label: Compiler and version + placeholder: Clang 18.1.0 validations: required: true + + - type: dropdown + id: cpp-standard + attributes: + label: C++ standard + options: + - 'C++17' + - 'C++20' + - 'C++23' + - 'C++26' + validations: + required: true + + - type: dropdown + id: arch + attributes: + label: Architecture + options: + - x86-64 + - x86 + - AArch64 / ARM + - RISC-V + - other + validations: + required: true + + - type: input + id: opt-level + attributes: + label: Optimization level and relevant flags + placeholder: '-O2 -ffp-contract=fast' + validations: + required: false + - type: input - id: operating-system + id: os attributes: - label: Operating System - description: - If applicable (i.e, an issue with how the code compiles for your OS) the OS version this bug is happening in - placeholder: Ubuntu Linux 22.04 + label: Operating system + placeholder: Ubuntu 24.04 validations: required: false + - type: textarea - id: screenshots + id: context attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. - placeholder: As you can see here, the output is not showing up - value: Not applicable + label: Additional context validations: required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d6963368 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Question or design discussion + url: https://discord.gg/p3mVxAbdmc + about: Usage questions, design discussion, and general help belong on Discord. + - name: Security vulnerability + url: https://github.com/Rinzii/ccmath/security/policy + about: Report security issues privately as described in SECURITY.md. Do not open a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..df8a983b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,51 @@ +name: Feature Request +description: Propose a new function, behavior, or capability for ccmath +title: '[FEATURE]: ' +labels: ['T: New Feature', 'S: Untriaged'] +body: + - type: checkboxes + attributes: + label: Due diligence + options: + - label: I searched existing issues and did not find a duplicate + required: true + + - type: textarea + id: summary + attributes: + label: Summary + description: What should ccmath do that it does not do today. + validations: + required: true + + - type: input + id: function + attributes: + label: Function or area + description: The function or family this concerns, if applicable. + placeholder: ccm::lgamma, or trig + validations: + required: false + + - type: textarea + id: standard + attributes: + label: Standard reference + description: The relevant C++ standard clause or cppreference page, if this maps to a standard function. + validations: + required: false + + - type: textarea + id: motivation + attributes: + label: Motivation + description: The problem this solves and why it belongs in ccmath. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 70e35e20..84e11c2f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1 +1,30 @@ - + + +## Summary + + + +## How tested + + + +## Checklist + +- [ ] Targets `main` (or a `release/N.x` branch for a backport) +- [ ] Tests added or updated in this change +- [ ] No `` include added under `include/` +- [ ] Formatting and lint pass locally + +### For numeric or kernel changes + + + +- [ ] Standard edge cases handled (NaN, signed zero, infinities, domain and range) +- [ ] Accuracy within the 4 ULP policy, validated against an oracle (MPFR, Sollya, or Gappa) with the source of truth noted +- [ ] Any accuracy or rounding-mode compromise is documented at the function with a `TODO` and a tracking issue +- [ ] Boundary inputs tested, not a single point (overflow, underflow, subnormals) +- [ ] The test reaches the changed kernel via `constexpr`/`static_assert` or a direct `ccm::gen::*_gen` call, not only a runtime call that may resolve to a builtin +- [ ] Every instantiated type covered (`float` and `double` at a minimum) +- [ ] Approach agreed on the issue first, for work labeled `D0`/`D1` or a numeric-rigor `A:` area + + diff --git a/.github/actions/install-clang-format/action.yml b/.github/actions/install-clang-format/action.yml new file mode 100644 index 00000000..f359f74d --- /dev/null +++ b/.github/actions/install-clang-format/action.yml @@ -0,0 +1,48 @@ +name: Install clang-format +description: Install clang-format 22.1.6 for CI format checks with download retries on macOS and Windows + +runs: + using: composite + steps: + - name: Read clang-format version + id: clang_format_version + shell: bash + run: echo "value=$(tr -d '[:space:]' < tools/clang_format_version)" >> "${GITHUB_OUTPUT}" + + - name: Install clang-format (Linux) + if: runner.os == 'Linux' + shell: bash + run: bash tools/install_clang_format_linux.sh + + - name: Install clang-format (attempt 1) + if: runner.os != 'Linux' + uses: aminya/setup-cpp@1f17f92d6a52bfcb1a25348e2c526c2e5cbb1134 # v1.8.0 + continue-on-error: true + id: clang_format_attempt_1 + with: + clangformat: ${{ steps.clang_format_version.outputs.value }} + + - name: Wait before clang-format retry + if: runner.os != 'Linux' && steps.clang_format_attempt_1.outcome != 'success' + shell: bash + run: sleep 30 + + - name: Install clang-format (attempt 2) + if: runner.os != 'Linux' && steps.clang_format_attempt_1.outcome != 'success' + uses: aminya/setup-cpp@1f17f92d6a52bfcb1a25348e2c526c2e5cbb1134 # v1.8.0 + continue-on-error: true + id: clang_format_attempt_2 + with: + clangformat: ${{ steps.clang_format_version.outputs.value }} + + - name: Wait before clang-format retry + if: runner.os != 'Linux' && steps.clang_format_attempt_1.outcome != 'success' && steps.clang_format_attempt_2.outcome != 'success' + shell: bash + run: sleep 60 + + - name: Install clang-format (attempt 3) + if: runner.os != 'Linux' && steps.clang_format_attempt_1.outcome != 'success' && steps.clang_format_attempt_2.outcome != 'success' + uses: aminya/setup-cpp@1f17f92d6a52bfcb1a25348e2c526c2e5cbb1134 # v1.8.0 + id: clang_format_attempt_3 + with: + clangformat: ${{ steps.clang_format_version.outputs.value }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f5f608fc..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" # Necessary to update action hashes. - directory: "/" - schedule: - interval: "monthly" - # Allow up to 3 opened pull requests for github-actions versions. - open-pull-requests-limit: 3 - target-branch: "dev" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..2b0c9500 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,119 @@ +# Path-based labels applied by .github/workflows/labeler-pr.yml (actions/labeler). +# Helper file for that workflow. Does nothing on its own. + +"A: Basic": + - changed-files: + - any-glob-to-any-file: + - "**/basic/**" + - "**/basic.hpp" + +"A: Compare": + - changed-files: + - any-glob-to-any-file: + - "**/compare/**" + - "**/compare.hpp" + +"A: Expo": + - changed-files: + - any-glob-to-any-file: + - "**/expo/**" + - "**/expo.hpp" + +"A: Fmanip": + - changed-files: + - any-glob-to-any-file: + - "**/fmanip/**" + - "**/fmanip.hpp" + +"A: Hyper": + - changed-files: + - any-glob-to-any-file: + - "**/hyper/**" + - "**/hyper.hpp" + +"A: Misc": + - changed-files: + - any-glob-to-any-file: + - "**/misc/**" + +"A: Nearest": + - changed-files: + - any-glob-to-any-file: + - "**/nearest/**" + - "**/nearest.hpp" + +"A: Power": + - changed-files: + - any-glob-to-any-file: + - "**/power/**" + - "**/power.hpp" + +"A: Special": + - changed-files: + - any-glob-to-any-file: + - "**/special/**" + - "**/special.hpp" + +"A: Trig": + - changed-files: + - any-glob-to-any-file: + - "**/trig/**" + - "**/trig.hpp" + +"A: SIMD": + - changed-files: + - any-glob-to-any-file: + - "**/runtime/simd/**" + - "**/runtime/pp/**" + - "**/intrin/**" + +"A: Runtime Kernel": + - changed-files: + - any-glob-to-any-file: + - "**/internal/math/runtime/**" + +"A: Build": + - changed-files: + - any-glob-to-any-file: + - "**/CMakeLists.txt" + - "**/*.cmake" + - "cmake/**" + - "CMakePresets.json" + - "meson.build" + - "meson_options.txt" + - "premake5.lua" + +"A: Tooling": + - changed-files: + - any-glob-to-any-file: + - "tools/**" + - "benchmarks/**" + - "fuzzing/**" + - "integration/**" + - "examples/**" + - "showcase/**" + +"A: Numeric Proof": + - changed-files: + - any-glob-to-any-file: + - "docs/proofs/**" + - "docs/approximating_functions/sollya/**" + - "docs/approximating_functions/gappa/**" + +"T: Tests": + - changed-files: + - any-glob-to-any-file: + - "tests/**" + +"T: Documentation": + - changed-files: + - any-glob-to-any-file: + - "**/*.md" + - "docs/**" + +"Changes: Public API": + - changed-files: + - any-glob-to-any-file: + - "include/ccmath/*.hpp" + - "include/ccmath/math/**" + - "include/ccmath/ext/**" diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 00000000..0b095c6d --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,246 @@ +# Source of truth for repository labels. Synced by .github/workflows/labels-sync.yml. +# Edit here and open a PR; do not edit labels in the GitHub UI. + +- name: "A: Accuracy/ULP" + color: "d6fcff" + description: "Out-of-contract ULP error." +- name: "A: Basic" + color: "d6fcff" + description: "Area: basic math functions." +- name: "A: Build" + color: "2c1183" + description: "Build, CI, and project generation (CMake/Meson/premake)." +- name: "A: Compare" + color: "d6fcff" + description: "Area: comparison functions." +- name: "A: Constexpr" + color: "d6fcff" + description: "Compile-time evaluation path." +- name: "A: Correctly Rounded" + color: "d6fcff" + description: "Correct-rounding work or CR regressions." +- name: "A: Deterministic" + color: "d6fcff" + description: "CCMATH_ENABLE_DETERMINISTIC behavior." +- name: "A: Edge Cases" + color: "d6fcff" + description: "Sign of zero, NaN/inf, standards conformance." +- name: "A: Expo" + color: "d6fcff" + description: "Area: exponential and logarithmic functions." +- name: "A: Fmanip" + color: "d6fcff" + description: "Area: floating-point manipulation functions." +- name: "A: Hyper" + color: "d6fcff" + description: "Area: hyperbolic functions." +- name: "A: Misc" + color: "d6fcff" + description: "Area: miscellaneous functions." +- name: "A: Nearest" + color: "d6fcff" + description: "Area: rounding and nearest-integer functions." +- name: "A: Numeric Proof" + color: "d6fcff" + description: "Sollya/Gappa/MPFR validation owed or in progress." +- name: "A: Overflow/Underflow" + color: "d6fcff" + description: "Range and clamp behavior at the extremes." +- name: "A: Power" + color: "d6fcff" + description: "Area: power and root functions." +- name: "A: Range Reduction" + color: "d6fcff" + description: "Argument reduction (Payne-Hanek, Cody-Waite)." +- name: "A: Rounding Modes" + color: "d6fcff" + description: "Directed rounding and fenv flag behavior." +- name: "A: Runtime Kernel" + color: "d6fcff" + description: "Generic runtime path." +- name: "A: SIMD" + color: "d6fcff" + description: "Vectorized path." +- name: "A: Special" + color: "d6fcff" + description: "Area: special functions." +- name: "A: Tooling" + color: "d6fcff" + description: "Benchmarks, asmlab, and developer tooling." +- name: "A: Trig" + color: "d6fcff" + description: "Area: trigonometric functions." +- name: "Arch: AArch64" + color: "c2e0c6" + description: "AArch64 and ARM specific behavior." +- name: "Arch: RISC-V" + color: "c2e0c6" + description: "RISC-V specific behavior." +- name: "Arch: x86" + color: "c2e0c6" + description: "x86 and x86-64 specific behavior." +- name: "Branch: main" + color: "1d76db" + description: "Pull request targets the main trunk." +- name: "Branch: release" + color: "5319e7" + description: "Pull request targets a release/N.x branch." +- name: "Bug: Needs Replicating" + color: "0e8a16" + description: "Still needs reproduction by at least one other person." +- name: "Bug: Replicated" + color: "0e8a16" + description: "Reproduced by at least one other person." +- name: "Changes: Headers Only" + color: "c5def5" + description: "Changes are limited to header files." +- name: "Changes: Public API" + color: "c5def5" + description: "Changes touch the public API surface." +- name: "Compiler: Clang" + color: "bfd4f2" + description: "Clang-specific behavior or support." +- name: "Compiler: GCC" + color: "bfd4f2" + description: "GCC-specific behavior or support." +- name: "Compiler: MSVC" + color: "bfd4f2" + description: "MSVC-specific behavior or support." +- name: "D0: Irrational" + color: "c603fc" + description: "A cursed correctly-rounded or hard-to-round problem that may take unreasonable effort." +- name: "D1: Very High" + color: "b60205" + description: "Requires deep floating-point expertise and a complex or specialised algorithm." +- name: "D2: High" + color: "d93f0b" + description: "Requires deep floating-point and codebase knowledge." +- name: "D3: Medium" + color: "fbca04" + description: "Requires a fair amount of numeric and codebase knowledge." +- name: "D4: Low" + color: "84dc3e" + description: "Reasonably approachable for newer contributors." +- name: "D5: Trivial" + color: "c2e0c6" + description: "Mostly bit or exponent manipulation, approachable with little numeric background." +- name: "Feature: Approved" + color: "0e8a16" + description: "Feature request approved by an admin. Work may proceed." +- name: "Feature: Unapproved" + color: "fbca04" + description: "Feature request awaiting admin approval before work begins." +- name: "meta-issue" + color: "d4c5f9" + description: "Umbrella issue tracking work across multiple sub-issues or pull requests." +- name: "P1: Critical" + color: "b60205" + description: "Needs immediate attention." +- name: "P2: High" + color: "d93f0b" + description: "Important and should be addressed soon." +- name: "P3: Standard" + color: "fef2c0" + description: "Normal priority." +- name: "P4: Low" + color: "ededed" + description: "Lower priority and not time-sensitive." +- name: "Platform: Linux" + color: "5319e7" + description: "Linux-specific behavior or support." +- name: "Platform: Other" + color: "5319e7" + description: "Other platform-specific behavior or support." +- name: "Platform: Windows" + color: "5319e7" + description: "Windows-specific behavior or support." +- name: "Platform: macOS" + color: "5319e7" + description: "macOS-specific behavior or support." +- name: "S: Approved" + color: "0e8a16" + description: "Has an approved review." +- name: "S: Awaiting Changes" + color: "b60205" + description: "Needs changes before review continues." +- name: "S: Blocked" + color: "fbca04" + description: "Blocked by another issue or pull request." +- name: "S: DO NOT MERGE" + color: "b60205" + description: "Must not be merged." +- name: "S: Help Wanted" + color: "0e8a16" + description: "Additional help is wanted." +- name: "S: Merge Conflict" + color: "d73a4a" + description: "Has merge conflicts." +- name: "S: Needs Discussion" + color: "fbca04" + description: "Needs more discussion before action is clear." +- name: "S: Needs Review" + color: "fbca04" + description: "Ready for review." +- name: "S: Stale" + color: "888888" + description: "Has gone inactive." +- name: "S: Untriaged" + color: "ededed" + description: "Not yet triaged." +- name: "S: Won't Fix" + color: "9a5399" + description: "This will not be fixed." +- name: "T: Bugfix" + color: "fff7b0" + description: "Bugs and bug fixes." +- name: "T: Chore" + color: "562d4b" + description: "Routine maintenance and upkeep." +- name: "T: Cleanup" + color: "fff7b0" + description: "Code cleanup." +- name: "T: Documentation" + color: "fff7b0" + description: "Documentation changes." +- name: "T: New Feature" + color: "fff7b0" + description: "New functionality." +- name: "T: Performance" + color: "fff7b0" + description: "Performance issues or optimizations." +- name: "T: Refactor" + color: "fff7b0" + description: "Refactoring existing code." +- name: "T: Tests" + color: "91e343" + description: "Test additions, fixes, or coverage work." +- name: "ai-assisted" + color: "ededed" + description: "Contribution used AI assistance." +- name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists." +- name: "good first issue" + color: "7057ff" + description: "Good for newcomers." +- name: "invalid" + color: "e4e669" + description: "This does not seem right." +- name: "question" + color: "d876e3" + description: "Further information is requested." +- name: "size/L" + color: "ededed" + description: "Denotes a PR that changes 1000-4999 lines." +- name: "size/M" + color: "ededed" + description: "Denotes a PR that changes 100-999 lines." +- name: "size/S" + color: "ededed" + description: "Denotes a PR that changes 10-99 lines." +- name: "size/XL" + color: "ededed" + description: "Denotes a PR that changes 5000+ lines." +- name: "size/XS" + color: "ededed" + description: "Denotes a PR that changes 0-9 lines." diff --git a/.github/workflows/ci-integration.yml b/.github/workflows/ci-integration.yml new file mode 100644 index 00000000..65fb2236 --- /dev/null +++ b/.github/workflows/ci-integration.yml @@ -0,0 +1,85 @@ +name: ci-integration + +on: + push: + tags-ignore: + - v*.* + branches: + - '**' + pull_request: + branches: + - '**' + +permissions: + contents: read + +jobs: + vendored-integration: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Install dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -yqq --no-install-recommends ninja-build meson + + - name: Cache premake5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: .ci-cache/premake + key: premake-5.0.0-beta8-linux + + - name: Install premake5 + env: + PREMAKE_CACHE_DIR: ${{ github.workspace }}/.ci-cache/premake + run: | + mkdir -p .ci-cache/premake + bash tools/install_premake5_linux.sh .ci-cache/premake + sudo install -m 755 .ci-cache/premake/premake5 /usr/local/bin/premake5 + + - name: Configure CCMath with CMake + run: | + cmake --preset ninja-clang-debug \ + -DCCMATH_BUILD_TESTS=OFF \ + -DCCMATH_BUILD_EXAMPLES=OFF \ + -DCCMATH_BUILD_BENCHMARKS=OFF + + - name: Generate secondary build manifest + run: cmake --build out/build/ninja/ninja-clang-debug --target ccmath-generate-secondary-builds + + - name: Verify secondary build markers + run: | + python3 tools/gen_secondary_builds.py \ + --check \ + --manifest out/build/ninja/ninja-clang-debug/ccmath-build-manifest.json \ + --source-dir . \ + --output-dir out/build/ninja/ninja-clang-debug/gen-secondary + + - name: Prepare vendored CCMath tree + run: bash tools/integration/prepare_vendored_tree.sh out/build/ninja/ninja-clang-debug + + - name: CMake vendored integration + working-directory: integration/vendored-consumer + run: | + cmake -B build-cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug + cmake --build build-cmake + ./build-cmake/vendored-ccmath-consumer + + - name: Meson vendored integration + run: | + meson setup integration/vendored-consumer/build-meson integration/vendored-consumer + meson compile -C integration/vendored-consumer/build-meson + ./integration/vendored-consumer/build-meson/vendored-ccmath-consumer + + - name: Premake vendored integration + working-directory: integration/vendored-consumer + run: | + premake5 gmake + make config=debug + ./bin/Debug/vendored-ccmath-consumer diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index da5c6b73..ae4c3401 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -5,98 +5,393 @@ on: tags-ignore: - v*.* branches: - - '*' + - '**' pull_request: branches: - - '*' + - '**' permissions: contents: read jobs: - build: + build-cxx17: runs-on: ubuntu-latest + timeout-minutes: 30 strategy: - fail-fast: false + fail-fast: true matrix: include: - # GCC Debug - name: gcc-debug configurePreset: ninja-gcc-debug buildPreset: build-ninja-gcc-debug testPreset: test-ninja-gcc-debug cxx_version: 17 - - name: gcc-debug-cxx20 - configurePreset: ninja-gcc-debug - buildPreset: build-ninja-gcc-debug - testPreset: test-ninja-gcc-debug - cxx_version: 20 - - # GCC Release - name: gcc-release configurePreset: ninja-gcc-release buildPreset: build-ninja-gcc-release testPreset: test-ninja-gcc-release cxx_version: 17 + - name: clang-debug + configurePreset: ninja-clang-debug + buildPreset: build-ninja-clang-debug + testPreset: test-ninja-clang-debug + cxx_version: 17 + + - name: clang-release + configurePreset: ninja-clang-release + buildPreset: build-ninja-clang-release + testPreset: test-ninja-clang-release + cxx_version: 17 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Install dependencies + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxx20: + needs: build-cxx17 + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: gcc-debug-cxx20 + configurePreset: ninja-gcc-debug + buildPreset: build-ninja-gcc-debug + testPreset: test-ninja-gcc-debug + cxx_version: 20 + - name: gcc-release-cxx20 configurePreset: ninja-gcc-release buildPreset: build-ninja-gcc-release testPreset: test-ninja-gcc-release cxx_version: 20 - # Clang Debug - - name: clang-debug + - name: clang-debug-cxx20 configurePreset: ninja-clang-debug buildPreset: build-ninja-clang-debug testPreset: test-ninja-clang-debug - cxx_version: 17 + cxx_version: 20 - - name: clang-debug-cxx20 + - name: clang-release-cxx20 + configurePreset: ninja-clang-release + buildPreset: build-ninja-clang-release + testPreset: test-ninja-clang-release + cxx_version: 20 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Install dependencies + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxx23: + needs: build-cxx20 + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: gcc-debug-cxx23 + configurePreset: ninja-gcc-debug + buildPreset: build-ninja-gcc-debug + testPreset: test-ninja-gcc-debug + cxx_version: 23 + + - name: gcc-release-cxx23 + configurePreset: ninja-gcc-release + buildPreset: build-ninja-gcc-release + testPreset: test-ninja-gcc-release + cxx_version: 23 + + - name: clang-debug-cxx23 configurePreset: ninja-clang-debug buildPreset: build-ninja-clang-debug testPreset: test-ninja-clang-debug - cxx_version: 20 + cxx_version: 23 - # Clang Release - - name: clang-release + - name: clang-release-cxx23 configurePreset: ninja-clang-release buildPreset: build-ninja-clang-release testPreset: test-ninja-clang-release - cxx_version: 17 + cxx_version: 23 - - name: clang-release-cxx20 + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Install dependencies + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxxlatest: + needs: build-cxx23 + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # The default ubuntu-latest g++ is 13.3, which does not support C++26. + # GCC 14 is preinstalled on the image, so pin the gcc cells to it. + - name: gcc-debug-cxxlatest + configurePreset: ninja-gcc-debug + buildPreset: build-ninja-gcc-debug + testPreset: test-ninja-gcc-debug + cxx_version: 26 + compiler_args: -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 + + - name: gcc-release-cxxlatest + configurePreset: ninja-gcc-release + buildPreset: build-ninja-gcc-release + testPreset: test-ninja-gcc-release + cxx_version: 26 + compiler_args: -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 + + - name: clang-debug-cxxlatest + configurePreset: ninja-clang-debug + buildPreset: build-ninja-clang-debug + testPreset: test-ninja-clang-debug + cxx_version: 26 + compiler_args: "" + + - name: clang-release-cxxlatest configurePreset: ninja-clang-release buildPreset: build-ninja-clang-release testPreset: test-ninja-clang-release - cxx_version: 20 + cxx_version: 26 + compiler_args: "" steps: - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Cache build - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ./**/CMakeFiles ./**/CMakeCache.txt ./**/out - key: ${{ runner.os }}-${{ matrix.name }} + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- - name: Install dependencies - run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build g++-14 - name: Configure - run: cmake --preset="${{ matrix.configurePreset }}" -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + run: > + cmake --preset="${{ matrix.configurePreset }}" + ${{ matrix.compiler_args }} + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF - name: Build run: cmake --build --preset="${{ matrix.buildPreset }}" - name: Test - run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -F + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + rigorous: + needs: build-cxx17 + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache rigorous builds + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + build-rigorous + build-rigorous-nobuiltin + key: ${{ runner.os }}-rigorous-build-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'tests/**', 'include/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-rigorous-build- + + - name: Cache APT archives + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: /var/cache/apt/archives + key: ${{ runner.os }}-apt-mpfr-gmp-v1 + + - name: Cache CORE-MATH prefix + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: coremath-prefix + key: ${{ runner.os }}-coremath-prefix-v1-${{ hashFiles('cmake/coremath/**', 'tools/coremath/**') }} + + - name: Install dependencies + run: | + if ! sudo apt-get update -qq || ! sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev sollya gappa git; then + sudo apt-get install -yqq --no-install-recommends ninja-build pkg-config libmpfr-dev libgmp-dev git + fi + + - name: Build CORE-MATH oracle prefix + run: | + if [[ ! -f coremath-prefix/include/crmath.h ]]; then + bash tools/coremath/build_prefix.sh "${GITHUB_WORKSPACE}/coremath-prefix" + fi + + - name: Configure rigorous validation + run: > + cmake -S . -B build-rigorous -G Ninja + -DCCMATH_BUILD_TESTS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=OFF + -DCCMATH_BUILD_RIGOROUS_TESTS=ON + -DCCMATH_ENABLE_MPFR_TESTS=ON + -DCCMATH_ENABLE_COREMATH_TESTS=ON + -DCCMATH_COREMATH_ROOT=${{ github.workspace }}/coremath-prefix + -DCCMATH_ENABLE_SEARCH_TOOLS=ON + -DCCMATH_DESIRED_CXX_STANDARD=cxx_std_20 + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + + - name: Configure rigorous no-builtin validation + run: > + cmake -S . -B build-rigorous-nobuiltin -G Ninja + -DCCMATH_BUILD_TESTS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=OFF + -DCCMATH_BUILD_RIGOROUS_TESTS=ON + -DCCMATH_ENABLE_MPFR_TESTS=ON + -DCCMATH_ENABLE_COREMATH_TESTS=ON + -DCCMATH_COREMATH_ROOT=${{ github.workspace }}/coremath-prefix + -DCCMATH_ENABLE_SEARCH_TOOLS=ON + -DCCMATH_TEST_DISABLE_RUNTIME_BUILTIN=ON + -DCCMATH_DESIRED_CXX_STANDARD=cxx_std_20 + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + + - name: Build rigorous validation + run: cmake --build build-rigorous --parallel + + - name: Build rigorous no-builtin validation + run: cmake --build build-rigorous-nobuiltin --parallel + + - name: Run rigorous validation + run: ctest --test-dir build-rigorous --output-on-failure -L rigorous + + - name: Run rigorous no-builtin validation + run: ctest --test-dir build-rigorous-nobuiltin --output-on-failure -L rigorous + + - name: Run pow proof smoke + continue-on-error: true + run: cmake --build build-rigorous --target ccmath-rigorous-pow-proof-smoke diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 3febe67d..c47d0c4b 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -5,19 +5,20 @@ on: tags-ignore: - v*.* branches: - - '*' + - '**' pull_request: branches: - - '*' + - '**' permissions: contents: read jobs: - build: + build-cxx17: runs-on: macos-latest + timeout-minutes: 30 strategy: - fail-fast: false + fail-fast: true matrix: include: - name: clang-debug-cxx17 @@ -26,73 +27,323 @@ jobs: testPreset: test-ninja-clang-debug cxx_version: 17 - - name: clang-debug-cxx20 - configurePreset: ninja-clang-debug - buildPreset: build-ninja-clang-debug - testPreset: test-ninja-clang-debug - cxx_version: 20 - - name: clang-release-cxx17 configurePreset: ninja-clang-release buildPreset: build-ninja-clang-release testPreset: test-ninja-clang-release cxx_version: 17 + - name: gcc-debug-cxx17 + configurePreset: ninja-gcc-debug + buildPreset: build-ninja-gcc-debug + testPreset: test-ninja-gcc-debug + cxx_version: 17 + + - name: gcc-release-cxx17 + configurePreset: ninja-gcc-release + buildPreset: build-ninja-gcc-release + testPreset: test-ninja-gcc-release + cxx_version: 17 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxx20: + needs: build-cxx17 + runs-on: macos-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: clang-debug-cxx20 + configurePreset: ninja-clang-debug + buildPreset: build-ninja-clang-debug + testPreset: test-ninja-clang-debug + cxx_version: 20 + - name: clang-release-cxx20 configurePreset: ninja-clang-release buildPreset: build-ninja-clang-release testPreset: test-ninja-clang-release cxx_version: 20 - - name: gcc-debug-cxx17 + - name: gcc-debug-cxx20 configurePreset: ninja-gcc-debug buildPreset: build-ninja-gcc-debug testPreset: test-ninja-gcc-debug - cxx_version: 17 + cxx_version: 20 - - name: gcc-debug-cxx20 + - name: gcc-release-cxx20 + configurePreset: ninja-gcc-release + buildPreset: build-ninja-gcc-release + testPreset: test-ninja-gcc-release + cxx_version: 20 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxx23: + needs: build-cxx20 + runs-on: macos-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: clang-debug-cxx23 + configurePreset: ninja-clang-debug + buildPreset: build-ninja-clang-debug + testPreset: test-ninja-clang-debug + cxx_version: 23 + + - name: clang-release-cxx23 + configurePreset: ninja-clang-release + buildPreset: build-ninja-clang-release + testPreset: test-ninja-clang-release + cxx_version: 23 + + - name: gcc-debug-cxx23 configurePreset: ninja-gcc-debug buildPreset: build-ninja-gcc-debug testPreset: test-ninja-gcc-debug - cxx_version: 20 + cxx_version: 23 - - name: gcc-release-cxx17 + - name: gcc-release-cxx23 configurePreset: ninja-gcc-release buildPreset: build-ninja-gcc-release testPreset: test-ninja-gcc-release - cxx_version: 17 + cxx_version: 23 - - name: gcc-release-cxx20 + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxxlatest: + needs: build-cxx23 + runs-on: macos-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: clang-debug-cxxlatest + configurePreset: ninja-clang-debug + buildPreset: build-ninja-clang-debug + testPreset: test-ninja-clang-debug + cxx_version: 26 + + - name: clang-release-cxxlatest + configurePreset: ninja-clang-release + buildPreset: build-ninja-clang-release + testPreset: test-ninja-clang-release + cxx_version: 26 + + - name: gcc-debug-cxxlatest + configurePreset: ninja-gcc-debug + buildPreset: build-ninja-gcc-debug + testPreset: test-ninja-gcc-debug + cxx_version: 26 + + - name: gcc-release-cxxlatest configurePreset: ninja-gcc-release buildPreset: build-ninja-gcc-release testPreset: test-ninja-gcc-release - cxx_version: 20 + cxx_version: 26 steps: - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Cache build - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ./**/CMakeFiles ./**/CMakeCache.txt ./**/out - key: ${{ runner.os }}-${{ matrix.name }} - - - name: Install Ninja - run: brew install ninja + key: ${{ runner.os }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.name }}- - name: Configure - run: cmake --preset="${{ matrix.configurePreset }}" -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF - name: Build run: cmake --build --preset="${{ matrix.buildPreset }}" - name: Test - run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -F + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + rigorous: + needs: build-cxx17 + runs-on: macos-latest + timeout-minutes: 45 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache rigorous build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: build-rigorous + key: ${{ runner.os }}-rigorous-build-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'tests/**', 'include/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-rigorous-build- + + - name: Cache Homebrew downloads + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/Library/Caches/Homebrew + key: ${{ runner.os }}-brew-mpfr-gmp-ninja-pkgconfig-v1 + + - name: Cache CORE-MATH prefix + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: coremath-prefix + key: ${{ runner.os }}-coremath-prefix-v1-${{ hashFiles('cmake/coremath/**', 'tools/coremath/**') }} + + - name: Install dependencies + run: brew install mpfr gmp + + - name: Build CORE-MATH oracle prefix + run: | + if [[ ! -f coremath-prefix/include/crmath.h ]]; then + bash tools/coremath/build_prefix.sh "${GITHUB_WORKSPACE}/coremath-prefix" + fi + + - name: Configure rigorous validation + run: > + cmake -S . -B build-rigorous -G Ninja + -DCCMATH_BUILD_TESTS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=OFF + -DCCMATH_BUILD_RIGOROUS_TESTS=ON + -DCCMATH_ENABLE_MPFR_TESTS=ON + -DCCMATH_ENABLE_COREMATH_TESTS=ON + -DCCMATH_COREMATH_ROOT=${{ github.workspace }}/coremath-prefix + -DCCMATH_ENABLE_SEARCH_TOOLS=ON + -DCCMATH_DESIRED_CXX_STANDARD=cxx_std_20 + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + + - name: Build rigorous validation + run: cmake --build build-rigorous --parallel + + - name: Run rigorous validation + run: ctest --test-dir build-rigorous --output-on-failure -L rigorous diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 5c7d36b8..3b33cf8f 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -5,19 +5,20 @@ on: tags-ignore: - v*.* branches: - - '*' + - '**' pull_request: branches: - - '*' + - '**' permissions: contents: read jobs: - build: - runs-on: windows-latest + build-cxx17: + runs-on: windows-2022 + timeout-minutes: 45 strategy: - fail-fast: false + fail-fast: true matrix: include: - name: vs22-debug-cxx17 @@ -26,46 +27,247 @@ jobs: testPreset: test-vs22-debug cxx_version: 17 + - name: vs22-release-cxx17 + configurePreset: vs22-release + buildPreset: build-vs22-release + testPreset: test-vs22-release + cxx_version: 17 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}- + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxx20: + needs: build-cxx17 + runs-on: windows-2022 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: - name: vs22-debug-cxx20 configurePreset: vs22-debug buildPreset: build-vs22-debug testPreset: test-vs22-debug cxx_version: 20 - - name: vs22-release-cxx17 + - name: vs22-release-cxx20 configurePreset: vs22-release buildPreset: build-vs22-release testPreset: test-vs22-release - cxx_version: 17 + cxx_version: 20 - - name: vs22-release-cxx20 + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ./**/CMakeFiles + ./**/CMakeCache.txt + ./**/out + key: ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}- + + - name: Configure + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + + - name: Build + run: cmake --build --preset="${{ matrix.buildPreset }}" + + - name: Test + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + build-cxxlatest: + needs: build-cxx20 + runs-on: windows-2022 + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + # CMake has no cxx_std_26 mapping for MSVC; its newest is cxx_std_23, which maps + # to /std:c++latest. So C++latest on MSVC is requested as cxx_std_23. + include: + - name: vs22-debug-cxxlatest + configurePreset: vs22-debug + buildPreset: build-vs22-debug + testPreset: test-vs22-debug + cxx_version: 23 + + - name: vs22-release-cxxlatest configurePreset: vs22-release buildPreset: build-vs22-release testPreset: test-vs22-release - cxx_version: 20 + cxx_version: 23 steps: - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - name: Cache build - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ./**/CMakeFiles ./**/CMakeCache.txt ./**/out - key: ${{ runner.os }}-${{ matrix.name }} + key: ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}-${{ hashFiles('CMakeLists.txt', 'CMakePresets.json', 'cmake/**', 'include/**', 'tests/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ env.ImageVersion }}-${{ matrix.name }}- - name: Configure - run: cmake --preset="${{ matrix.configurePreset }}" -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + run: > + cmake --preset="${{ matrix.configurePreset }}" + -DCCMATH_DESIRED_CXX_STANDARD="cxx_std_${{ matrix.cxx_version }}" + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF - name: Build run: cmake --build --preset="${{ matrix.buildPreset }}" - name: Test - run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -F + run: ctest --preset="${{ matrix.testPreset }}" --output-on-failure --schedule-random -L simple -F + + rigorous: + needs: build-cxx20 + runs-on: windows-2022 + timeout-minutes: 90 + env: + CCMATH_VCPKG_BINARY_CACHE_DIR: ${{ github.workspace }}/.cache/vcpkg/archives + CCMATH_VCPKG_DOWNLOADS_DIR: ${{ github.workspace }}/.cache/vcpkg/downloads + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Cache vcpkg downloads + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ env.CCMATH_VCPKG_DOWNLOADS_DIR }} + key: ${{ runner.os }}-vcpkg-downloads-${{ hashFiles('cmake/vcpkg/vcpkg.json') }} + restore-keys: | + ${{ runner.os }}-vcpkg-downloads- + + - name: Cache vcpkg installed tree + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ github.workspace }}/cmake/vcpkg/vcpkg_installed + key: ${{ runner.os }}-vcpkg-installed-${{ hashFiles('cmake/vcpkg/vcpkg.json') }} + restore-keys: | + ${{ runner.os }}-vcpkg-installed- + + - name: Cache vcpkg binary archives + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ${{ env.CCMATH_VCPKG_BINARY_CACHE_DIR }} + key: ${{ runner.os }}-vcpkg-binary-${{ hashFiles('cmake/vcpkg/vcpkg.json') }} + restore-keys: | + ${{ runner.os }}-vcpkg-binary- + + - name: Bootstrap vcpkg + shell: pwsh + run: | + if (-not (Test-Path vcpkg)) { + git clone --depth 1 https://github.com/microsoft/vcpkg.git vcpkg + # Fetch the exact builtin-baseline commit so manifest version resolution can read + # versions/baseline.json from it; a depth-1 clone of HEAD alone does not contain it. + git -C vcpkg fetch --depth 1 origin 10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b + git -C vcpkg checkout 10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b + } + if (-not (Test-Path vcpkg/vcpkg.exe)) { + .\vcpkg\bootstrap-vcpkg.bat -disableMetrics + } + New-Item -ItemType Directory -Force -Path "${{ env.CCMATH_VCPKG_DOWNLOADS_DIR }}" | Out-Null + New-Item -ItemType Directory -Force -Path "${{ env.CCMATH_VCPKG_BINARY_CACHE_DIR }}" | Out-Null + echo "VCPKG_ROOT=$((Get-Location).Path)\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "VCPKG_DOWNLOADS=${{ env.CCMATH_VCPKG_DOWNLOADS_DIR }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "VCPKG_DEFAULT_BINARY_CACHE=${{ env.CCMATH_VCPKG_BINARY_CACHE_DIR }}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + echo "VCPKG_BINARY_SOURCES=clear;files,${{ env.CCMATH_VCPKG_BINARY_CACHE_DIR }},readwrite" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Cache rigorous build + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: out/build/vs22/vs22-release-rigorous + key: ${{ runner.os }}-${{ env.ImageVersion }}-vs22-release-rigorous-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'tests/**', 'include/**', 'thirdparty/**') }} + restore-keys: | + ${{ runner.os }}-${{ env.ImageVersion }}-vs22-release-rigorous- + + - name: Configure rigorous validation + shell: pwsh + run: | + cmake --preset=vs22-release-rigorous ` + -DCCMATH_DESIRED_CXX_STANDARD=cxx_std_20 ` + -DCCMATH_ENABLE_AGGRESSIVE_WARNINGS=ON ` + -DCCMATH_ENABLE_WARNINGS_AS_ERRORS=ON ` + -DCCMATH_BUILD_SIMPLE_TESTS=OFF ` + -DCCMATH_BUILD_RIGOROUS_TESTS=ON ` + -DCCMATH_ENABLE_MPFR_TESTS=ON ` + -DCCMATH_ENABLE_SEARCH_TOOLS=ON ` + -DCCMATH_ENABLE_COREMATH_TESTS=OFF ` + -DVCPKG_MANIFEST_DIR="${{ github.workspace }}/cmake/vcpkg" ` + -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" + + - name: Build rigorous validation + run: cmake --build --preset=build-vs22-release-rigorous --config Release + + - name: Run rigorous MPFR validation + run: ctest --preset=test-vs22-release-rigorous -C Release --output-on-failure -L rigorous diff --git a/.github/workflows/close-main-pr.yml b/.github/workflows/close-main-pr.yml new file mode 100644 index 00000000..ec74bdba --- /dev/null +++ b/.github/workflows/close-main-pr.yml @@ -0,0 +1,30 @@ +# Close PRs that were opened from a main development branch instead of a topic branch. + +name: Close PRs from main branches + +on: + pull_request_target: + types: + - opened + - ready_for_review + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + run: + if: ${{ (github.head_ref == 'main' || github.head_ref == 'master' || github.head_ref == 'develop') && github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + + steps: + - name: Close PR opened from a main development branch + uses: superbrothers/close-pull-request@9c18513d320d7b2c7185fb93396d0c664d5d8448 # v3 + with: + comment: | + Thank you for your contribution. + + It looks like this pull request was opened from a main development branch. That is something you should generally avoid, so this pull request has been automatically closed. + + Please move your work to a separate branch, then reopen the pull request from that branch. diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a9cf6cc3..13899813 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ "main", "dev" ] + branches: [ "main", "release/**" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main", "dev" ] + branches: [ "main", "release/**" ] schedule: - cron: "0 0 * * 1" @@ -30,16 +30,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/init@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -49,7 +49,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/autobuild@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -62,6 +62,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/analyze@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml deleted file mode 100644 index facdc26a..00000000 --- a/.github/workflows/dependency-review.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Dependency Review Action -# -# This Action will scan dependency manifest files that change as part of a Pull Request, -# surfacing known-vulnerable versions of the packages declared or updated in the PR. -# Once installed, if the workflow run is marked as required, -# PRs introducing known-vulnerable packages will be blocked from merging. -# -# Source repository: https://github.com/actions/dependency-review-action -name: 'Dependency Review' -on: [pull_request] - -permissions: - contents: read - -jobs: - dependency-review: - runs-on: ubuntu-latest - steps: - - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 - with: - egress-policy: audit - - - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: 'Dependency Review' - uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 diff --git a/.github/workflows/feature-approval.yml b/.github/workflows/feature-approval.yml new file mode 100644 index 00000000..133acee9 --- /dev/null +++ b/.github/workflows/feature-approval.yml @@ -0,0 +1,103 @@ +name: "Labels: Feature approval" + +on: + issues: + types: + - opened + - labeled + +permissions: + contents: read + issues: write + +jobs: + mark-unapproved: + if: "github.event.action == 'opened' || github.event.label.name == 'T: New Feature'" + runs-on: ubuntu-latest + + steps: + - name: Mark new feature requests as unapproved + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue = context.payload.issue; + const labels = issue.labels.map(l => l.name); + + if (!labels.includes('T: New Feature')) { + core.info('Not a feature request, nothing to do'); + return; + } + if (labels.includes('Feature: Approved')) { + core.info('Already approved, not marking unapproved'); + return; + } + if (labels.includes('Feature: Unapproved')) { + core.info('Already marked unapproved'); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['Feature: Unapproved'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: 'Thanks for the request. It is labeled `Feature: Unapproved` and waiting on a maintainer to approve it before implementation work begins.' + }); + + approve: + if: "github.event.action == 'labeled' && github.event.label.name == 'Feature: Approved'" + runs-on: ubuntu-latest + + steps: + - name: Clear unapproved when an admin approves + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const actor = context.actor; + const issue = context.payload.issue; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: actor + }); + + const admins = new Set(['admin', 'maintain']); + + if (!admins.has(permission.data.permission)) { + core.info(`${actor} is not an admin, reverting the approval`); + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'Feature: Approved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + return; + } + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + name: 'Feature: Unapproved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index ba9b0605..443a70ef 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -5,34 +5,132 @@ on: tags-ignore: - v*.* branches: - - '*' + - '**' pull_request: branches: - - '*' + - '**' + schedule: + # Nightly deep campaign (corpus-accumulating, artifact-uploading). + - cron: '0 5 * * *' + workflow_dispatch: permissions: contents: read +env: + # Bounded smoke budget per target. The nightly campaign runs far longer. + CCMATH_FUZZ_SMOKE_RUNS: 30000 + jobs: + # Bounded, deterministic regression gate. Runs both build flavors: + # - default: public dispatch (libm-backed transcendentals + complete kernels) + # - generic: the in-house kernels directly, the high-value differential target libfuzzer-smoke: - runs-on: macos-latest + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest] + fuzz_mode: [default, generic] + + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - name: Install dependencies + - name: Install dependencies (macOS) + if: runner.os == 'macOS' run: brew install ninja llvm lld - - name: Configure fuzz targets + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: > + sudo apt-get update -qq && + sudo apt-get install -yqq --no-install-recommends + cmake ninja-build git llvm-18 clang-18 lld-18 libclang-rt-18-dev + + - name: Configure fuzz targets (macOS) + if: runner.os == 'macOS' run: | LLVM_PREFIX="$(brew --prefix llvm)" cmake --preset=ninja-fuzz-debug \ -DCMAKE_C_COMPILER="${LLVM_PREFIX}/bin/clang" \ -DCMAKE_CXX_COMPILER="${LLVM_PREFIX}/bin/clang++" \ - -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \ + -DCCMATH_FUZZ_GENERIC=${{ matrix.fuzz_mode == 'generic' && 'ON' || 'OFF' }} + + - name: Configure fuzz targets (Linux) + if: runner.os == 'Linux' + run: | + cmake --preset=ninja-fuzz-debug \ + -DCMAKE_C_COMPILER=/usr/lib/llvm-18/bin/clang \ + -DCMAKE_CXX_COMPILER=/usr/lib/llvm-18/bin/clang++ \ + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld-18" \ + -DCCMATH_FUZZ_GENERIC=${{ matrix.fuzz_mode == 'generic' && 'ON' || 'OFF' }} - name: Build fuzz targets run: cmake --build --preset=build-ninja-fuzz-debug - name: Fuzz smoke tests run: ctest --preset=test-ninja-fuzz-debug -C Debug --output-on-failure + + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: fuzz-crashes-${{ matrix.os }}-${{ matrix.fuzz_mode }} + path: out/build/ninja/ninja-fuzz-debug/fuzzing/artifacts/**/crash-* + if-no-files-found: ignore + + # Nightly discovery: long campaigns with a persistent corpus. Non-blocking, since this is + # where new kernel bugs surface; reproducers are uploaded for triage. + libfuzzer-campaign: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + strategy: + fail-fast: false + matrix: + fuzz_mode: [default, generic] + + runs-on: ubuntu-latest + continue-on-error: true + env: + CCMATH_FUZZ_MAX_TOTAL_TIME: 120 + CCMATH_FUZZ_KEEP_CORPUS: 1 + + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Install dependencies + run: > + sudo apt-get update -qq && + sudo apt-get install -yqq --no-install-recommends + cmake ninja-build git llvm-18 clang-18 lld-18 libclang-rt-18-dev + + - name: Restore fuzz corpus + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: out/build/ninja/ninja-fuzz-debug/fuzzing/corpus + key: fuzz-corpus-${{ matrix.fuzz_mode }}-${{ github.run_id }} + restore-keys: | + fuzz-corpus-${{ matrix.fuzz_mode }}- + + - name: Configure fuzz targets + run: | + cmake --preset=ninja-fuzz-debug \ + -DCMAKE_C_COMPILER=/usr/lib/llvm-18/bin/clang \ + -DCMAKE_CXX_COMPILER=/usr/lib/llvm-18/bin/clang++ \ + -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld-18" \ + -DCCMATH_FUZZ_GENERIC=${{ matrix.fuzz_mode == 'generic' && 'ON' || 'OFF' }} + + - name: Build fuzz targets + run: cmake --build --preset=build-ninja-fuzz-debug + + - name: Run fuzz campaign + run: cmake --build --preset=build-ninja-fuzz-debug --target ccmath-fuzz-campaign-all + + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: fuzz-campaign-crashes-${{ matrix.fuzz_mode }} + path: out/build/ninja/ninja-fuzz-debug/fuzzing/artifacts/**/crash-* + if-no-files-found: ignore diff --git a/.github/workflows/labeler-branch.yml b/.github/workflows/labeler-branch.yml new file mode 100644 index 00000000..9703c838 --- /dev/null +++ b/.github/workflows/labeler-branch.yml @@ -0,0 +1,38 @@ +name: "Labels: Branch target" + +on: + pull_request_target: + types: + - opened + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-branch-label: + runs-on: ubuntu-latest + + steps: + - name: Label by base branch + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const base = context.payload.pull_request.base.ref; + const label = base.startsWith('release/') + ? 'Branch: release' + : (base === 'main' ? 'Branch: main' : null); + + if (!label) { + core.info(`No branch label for base ${base}`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: [label] + }); diff --git a/.github/workflows/labeler-changes-requested.yml b/.github/workflows/labeler-changes-requested.yml new file mode 100644 index 00000000..21f33074 --- /dev/null +++ b/.github/workflows/labeler-changes-requested.yml @@ -0,0 +1,65 @@ +name: "Labels: Awaiting Changes" + +on: + pull_request_review: + types: + - submitted + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-awaiting-changes: + if: github.event.review.state == 'changes_requested' + runs-on: ubuntu-latest + + steps: + - name: Add Awaiting Changes for trusted reviewers + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const review = context.payload.review; + const pr = context.payload.pull_request; + if (!review?.user?.login || pr?.number == null) { + core.info('Missing review user or pull request, skipping.'); + return; + } + + const reviewUser = review.user.login; + const issue_number = pr.number; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: reviewUser + }); + + const allowed = new Set(['admin', 'maintain', 'write']); + + if (!allowed.has(permission.data.permission)) { + core.info(`${reviewUser} does not have sufficient repo permissions`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Awaiting Changes'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Approved' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-conflict.yml b/.github/workflows/labeler-conflict.yml new file mode 100644 index 00000000..030407f3 --- /dev/null +++ b/.github/workflows/labeler-conflict.yml @@ -0,0 +1,31 @@ +name: Check Merge Conflicts + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: labeler-conflict-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + label: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Check for Merge Conflicts + uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0 + with: + dirtyLabel: "S: Merge Conflict" + repoToken: ${{ secrets.GITHUB_TOKEN }} + commentOnDirty: This pull request has conflicts, please resolve those before we can evaluate the pull request. diff --git a/.github/workflows/labeler-issue-form.yml b/.github/workflows/labeler-issue-form.yml new file mode 100644 index 00000000..c71b7c9c --- /dev/null +++ b/.github/workflows/labeler-issue-form.yml @@ -0,0 +1,64 @@ +name: "Labels: Issue form" + +on: + issues: + types: + - opened + - edited + +permissions: + contents: read + issues: write + +jobs: + autolabel: + if: "contains(github.event.issue.labels.*.name, 'T: Bugfix')" + runs-on: ubuntu-latest + + steps: + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Parse the bug report form + id: parse + uses: stefanbuck/github-issue-parser@cb6e97157cbf851e3a393ff8d57c93a484cc323f # v3 + with: + template-path: .github/ISSUE_TEMPLATE/bug_report.yml + + - name: Map form answers to labels + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + env: + FORM_JSON: ${{ steps.parse.outputs.jsonString }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const data = JSON.parse(process.env.FORM_JSON); + + const familyMap = { + basic: 'A: Basic', compare: 'A: Compare', expo: 'A: Expo', + fmanip: 'A: Fmanip', hyper: 'A: Hyper', misc: 'A: Misc', + nearest: 'A: Nearest', power: 'A: Power', special: 'A: Special', + trig: 'A: Trig' + }; + const archMap = { + 'x86-64': 'Arch: x86', 'x86': 'Arch: x86', + 'AArch64 / ARM': 'Arch: AArch64', 'RISC-V': 'Arch: RISC-V' + }; + + const toAdd = new Set(); + const family = (data.family || '').trim().toLowerCase(); + if (familyMap[family]) toAdd.add(familyMap[family]); + const arch = (data.arch || '').trim(); + if (archMap[arch]) toAdd.add(archMap[arch]); + + if (toAdd.size === 0) { + core.info('No mappable form fields, nothing to add'); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: [...toAdd] + }); diff --git a/.github/workflows/labeler-needsreview.yml b/.github/workflows/labeler-needsreview.yml new file mode 100644 index 00000000..654249c5 --- /dev/null +++ b/.github/workflows/labeler-needsreview.yml @@ -0,0 +1,45 @@ +name: "Labels: Needs Review" + +on: + pull_request_target: + types: + - opened + - review_requested + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + update-review-labels: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + steps: + - name: Add Needs Review and remove Awaiting Changes + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue_number = context.payload.pull_request.number; + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Needs Review'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Awaiting Changes' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-pr.yml b/.github/workflows/labeler-pr.yml new file mode 100644 index 00000000..6eaccc3d --- /dev/null +++ b/.github/workflows/labeler-pr.yml @@ -0,0 +1,23 @@ +name: "Labels: PR" + +on: + pull_request_target: + +permissions: + contents: read + pull-requests: write + +concurrency: + group: labeler-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + labeler: + runs-on: ubuntu-latest + + steps: + - name: Apply path-based labels + uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler.yml diff --git a/.github/workflows/labeler-review.yml b/.github/workflows/labeler-review.yml new file mode 100644 index 00000000..aac085b0 --- /dev/null +++ b/.github/workflows/labeler-review.yml @@ -0,0 +1,65 @@ +name: "Labels: Approved" + +on: + pull_request_review: + types: + - submitted + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + add-approved-label: + if: github.event.review.state == 'approved' + runs-on: ubuntu-latest + + steps: + - name: Add Approved label for trusted reviewers + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const review = context.payload.review; + const pr = context.payload.pull_request; + if (!review?.user?.login || pr?.number == null) { + core.info('Missing review user or pull request, skipping.'); + return; + } + + const reviewUser = review.user.login; + const issue_number = pr.number; + + const permission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username: reviewUser + }); + + const allowed = new Set(['admin', 'maintain', 'write']); + + if (!allowed.has(permission.data.permission)) { + core.info(`${reviewUser} does not have sufficient repo permissions`); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Approved'] + }); + + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + name: 'S: Needs Review' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } diff --git a/.github/workflows/labeler-size.yml b/.github/workflows/labeler-size.yml new file mode 100644 index 00000000..319f2d71 --- /dev/null +++ b/.github/workflows/labeler-size.yml @@ -0,0 +1,33 @@ +name: "Labels: Size" + +on: + pull_request_target: + +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: labeler-size-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + size-label: + runs-on: ubuntu-latest + + steps: + - name: Apply size label + uses: pascalgn/size-label-action@56b489b027932ec0cf60438a1a5f1a19c8fc71ff # v0.5.7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Values are suffixes only. The action applies labels as size/ (size/XS, size/S, and so on). + sizes: > + { + "0": "XS", + "10": "S", + "100": "M", + "1000": "L", + "5000": "XL" + } diff --git a/.github/workflows/labeler-stale.yml b/.github/workflows/labeler-stale.yml new file mode 100644 index 00000000..e29bc772 --- /dev/null +++ b/.github/workflows/labeler-stale.yml @@ -0,0 +1,33 @@ +name: "Labels: Stale" + +on: + schedule: + - cron: "0 6 * * *" + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + stale: + runs-on: ubuntu-latest + + steps: + - name: Mark inactive issues and pull requests + uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: -1 + stale-issue-label: "S: Stale" + stale-pr-label: "S: Stale" + remove-stale-when-updated: true + exempt-draft-pr: true + exempt-all-assignees: true + exempt-all-milestones: true + exempt-issue-labels: "P1: Critical, D0: Irrational, S: Blocked" + exempt-pr-labels: "P1: Critical, S: Blocked, S: Approved" + stale-issue-message: "This issue has been inactive for 60 days and is now marked stale. Comment to keep it open." + stale-pr-message: "This pull request has been inactive for 60 days and is now marked stale. Push or comment to keep it open." diff --git a/.github/workflows/labeler-untriaged.yml b/.github/workflows/labeler-untriaged.yml new file mode 100644 index 00000000..44258cf3 --- /dev/null +++ b/.github/workflows/labeler-untriaged.yml @@ -0,0 +1,79 @@ +name: "Labels: Untriaged" + +on: + issues: + types: + - opened + - labeled + +permissions: + contents: read + issues: write + +jobs: + add-untriaged: + if: github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - name: Add Untriaged label when no labels exist + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labels = + context.payload.issue?.labels ?? + context.payload.pull_request?.labels ?? + []; + + if (labels.length !== 0) { + core.info('Issue or pull request already has labels'); + return; + } + + const issue_number = + context.payload.issue?.number ?? + context.payload.pull_request?.number; + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number, + labels: ['S: Untriaged'] + }); + + remove-untriaged-when-other-label-added: + if: "github.event.action == 'labeled' && github.event.label.name != 'S: Untriaged'" + runs-on: ubuntu-latest + + steps: + - name: "Remove S: Untriaged when any other label is added" + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const untriaged = 'S: Untriaged'; + const item = + context.payload.issue ?? context.payload.pull_request; + if (!item) { + core.setFailed('No issue or pull request in payload'); + return; + } + + if (context.actor.endsWith('[bot]')) { + core.info(`Label added by ${context.actor}, leaving S: Untriaged for a human to clear`); + return; + } + + const hasUntriaged = item.labels.some(l => l.name === untriaged); + if (!hasUntriaged) { + core.info('S: Untriaged not present, nothing to remove'); + return; + } + + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: item.number, + name: untriaged + }); diff --git a/.github/workflows/labels-sync.yml b/.github/workflows/labels-sync.yml new file mode 100644 index 00000000..3a1ced7e --- /dev/null +++ b/.github/workflows/labels-sync.yml @@ -0,0 +1,34 @@ +name: "Labels: Sync" + +on: + push: + branches: + - main + paths: + - .github/labels.yml + - .github/workflows/labels-sync.yml + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + sync: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Sync labels from .github/labels.yml + uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 # v5 + with: + yaml-file: .github/labels.yml + skip-delete: false + dry-run: false diff --git a/.github/workflows/lint-actions.yml b/.github/workflows/lint-actions.yml new file mode 100644 index 00000000..6ec9334d --- /dev/null +++ b/.github/workflows/lint-actions.yml @@ -0,0 +1,38 @@ +name: "Lint: Actions" + +on: + push: + branches: + - main + paths: + - .github/workflows/** + - .github/actions/** + pull_request: + paths: + - .github/workflows/** + - .github/actions/** + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + actionlint: + runs-on: ubuntu-latest + + steps: + - name: Harden the runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Check out the repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Run actionlint + uses: reviewdog/action-actionlint@6fb7acc99f4a1008869fa8a0f09cfca740837d9d # v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + reporter: github-check + fail_on_error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2da0576a..899f67fc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,25 +1,113 @@ name: lint on: - pull_request: + push: + tags-ignore: + - v*.* + branches: + - '**' paths: - - '**.hpp' + - '.clang-format' + - '.github/actions/install-clang-format/**' + - '.github/workflows/lint.yml' + - '**/*.hpp' + - '**/*.cpp' + - '**/*.h' + - 'tools/apply_format.sh' + - 'tools/clang_format_version' + - 'tools/ensure_format.sh' + - 'tools/clang_tidy_version' + - 'tools/install_clang_tidy_linux.sh' + - 'tools/run_clang_tidy.sh' + - '.clang-tidy' + pull_request: + branches: + - '**' permissions: contents: read +concurrency: + group: lint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: - format_code: + format_check: + if: github.event_name == 'pull_request' + permissions: + contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - uses: ./.github/actions/install-clang-format + + - name: Check clang-format + run: bash tools/ensure_format.sh - - name: Install clang-format - uses: aminya/setup-cpp@3aac37c3fbde6cb1d2fa0a0a7e945b63b10561d5 # v1.1.0 + format_autofix: + if: github.event_name == 'push' + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - clangformat: 17.0.5 + ref: ${{ github.ref_name }} + fetch-depth: 0 + + - uses: ./.github/actions/install-clang-format + + - name: Check clang-format + id: check_format + run: bash tools/ensure_format.sh + continue-on-error: true + + - name: Apply clang-format + if: steps.check_format.outcome == 'failure' + run: bash tools/apply_format.sh - - name: Run clang-format + - name: Commit clang-format fixes + if: steps.check_format.outcome == 'failure' run: | - find include -name '*.hpp' | xargs clang-format -i -style=file -fallback-style=none - git diff --exit-code + if git diff --quiet -- '*.hpp' '*.cpp' '*.h'; then + echo "No formatting changes to commit." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add '*.hpp' '*.cpp' '*.h' + git commit -m "ci: apply clang-format" + git push origin HEAD:${{ github.ref_name }} + + - name: Verify clang-format after auto-fix + if: steps.check_format.outcome == 'failure' + run: bash tools/ensure_format.sh + + clang_tidy: + if: github.event_name == 'pull_request' + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Install dependencies + run: sudo apt-get update -qq && sudo apt-get install -yqq --no-install-recommends ninja-build + + - name: Install clang-tidy + run: bash tools/install_clang_tidy_linux.sh + + - name: Configure for compile database + run: > + cmake -S . -B build-tidy -G Ninja + -DCCMATH_BUILD_TESTS=ON + -DCCMATH_BUILD_SIMPLE_TESTS=ON + -DCCMATH_BUILD_RIGOROUS_TESTS=OFF + -DCCMATH_ENABLE_MPFR_TESTS=OFF + -DCCMATH_ENABLE_SEARCH_TOOLS=OFF + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + + - name: Run clang-tidy + run: bash tools/run_clang_tidy.sh build-tidy/compile_commands.json diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 7003d8e3..1aab3f24 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,18 +32,18 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@v2.16.0 + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -62,7 +62,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b56ba49b26e50535fa1e7f7db0f4f7b4bf65d80d # v3.28.10 + uses: github/codeql-action/upload-sarif@dd903d2e4f5405488e5ef1422510ee31c8b32357 # v3.36.2 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 24dd2b7e..4abc396a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,48 @@ **/.vscode/* **/.idea/* .idea/* +!.idea/cmake.xml .vs/* .vscode/* build/* +/build-*/ /out/ +mpfr-pow*-summary.json +mpfr-powf*-summary.json +mpfr-powl-summary.json +coremath-pow*-summary.json +coremath-powf*-summary.json +coremath-prefix/ +cmake/vcpkg/vcpkg_installed/ +vcpkg_installed/ +pow-search-results.json +powl-platform.json +tests/rigorous/oracle_logs/ +showcase/reports/ +showcase/compile_time/probes/ +path-matrix-json*/ .cache + +# LaTeX build artifacts (.tex and .bib remain tracked) +*.aux +*.bbl +*.blg +*.fdb_latexmk +*.fls +*.synctex.gz +*.toc +docs/proofs/**/*.pdf +tools/proofs/**/build/ + +# Sollya approximating_functions regenerated outputs (see docs/approximating_functions/README.md) +docs/approximating_functions/logs/ +docs/approximating_functions/data/ + +# Local duplicate approximation tree (canonical: docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf) +docs/approximation_guide/ .DS_Store +__pycache__/ +*.py[cod] # User Specific Configuration Files CMakeSettings.json @@ -70,3 +106,17 @@ fuzzing/oom-* fuzzing/slow-unit-* fuzzing/minimize-crash-* +# asmlab cached reference data (Agner Fog tables, uops.info exports); fetched on demand +tools/asmlab/refdata/* +!tools/asmlab/refdata/.gitkeep + +# asmlab rendered harness TUs (from tools/asmlab/**/*.cpp.in); primary output is under /out/ +tools/asmlab/harness/harness.cpp +tools/asmlab/harness/constexpr_probe.cpp +tools/asmlab/golden/harness.cpp + +# Premake generated artifacts at the repository root +/Makefile +/bin/ +/obj/ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b4f0da7..ab164801 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.18.0) # Project Setup and Metadata # ---------------------------------------------------------------------- set(CCMATH_TARGET_NAME "ccmath") -set(CCMATH_BUILD_VERSION 0.2.0) +set(CCMATH_BUILD_VERSION 0.3.0) project( ${CCMATH_TARGET_NAME} @@ -39,6 +39,13 @@ message(STATUS "CCMath: Version: ${CCMATH_BUILD_VERSION}") # Load user-configurable options (e.g., BUILD_EXAMPLES, BUILD_TESTS, etc.) include(cmake/config/UserOptions.cmake) +# Deterministic mode requires every call to route through the generic kernels, so +# runtime SIMD (a separate, non-generic evaluation path) must be off regardless of +# how CCMATH_DISABLE_RUNTIME_SIMD was set. +if (CCMATH_ENABLE_DETERMINISTIC) + set(CCMATH_DISABLE_RUNTIME_SIMD ON CACHE BOOL "Disabled by deterministic mode" FORCE) +endif () + if (CCMATH_DISABLE_RUNTIME_SIMD) set(CCMATH_ENABLE_RUNTIME_SIMD OFF) else () @@ -127,6 +134,11 @@ set(CCMATH_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR}) set(CCMATH_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH}) configure_file("${PROJECT_SOURCE_DIR}/cmake/in/version.hpp.in" "${PROJECT_BINARY_DIR}/include/ccmath/version.hpp") +if (CCMATH_PROJECT_IS_TOP_LEVEL) + include(cmake/config/EmitBuildManifest.cmake) + ccmath_setup_secondary_build_generation() +endif () + # ---------------------------------------------------------------------- # Installation Rules @@ -140,7 +152,7 @@ endif () # Subdirectories for Examples, Benchmarks, and Tests # ---------------------------------------------------------------------- # Only add these if the user explicitly asked for them in the options. -if (CCMATH_BUILD_EXAMPLES OR CCMATH_BUILD_BENCHMARKS OR CCMATH_BUILD_TESTS) +if (CCMATH_BUILD_EXAMPLES OR CCMATH_BUILD_BENCHMARKS OR CCMATH_BUILD_TESTS OR CCMATH_BUILD_SHOWCASE) # Use SYSTEM to suppress warnings from external code if supported by cmake if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) add_subdirectory(thirdparty SYSTEM) @@ -175,6 +187,15 @@ if (CCMATH_BUILD_EXAMPLES OR CCMATH_BUILD_BENCHMARKS OR CCMATH_BUILD_TESTS) else () add_subdirectory(tests) endif () + include(cmake/config/TestCtestTargets.cmake) + endif () + + if (CCMATH_BUILD_SHOWCASE) + if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + add_subdirectory(showcase SYSTEM) + else () + add_subdirectory(showcase) + endif () endif () endif () diff --git a/CMakePresets.json b/CMakePresets.json index bf5a3fb1..7a18a561 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,5 +1,5 @@ { - "version": 2, + "version": 6, "cmakeMinimumRequired": { "major": 3, "minor": 18, @@ -19,6 +19,16 @@ "CMAKE_CXX_STANDARD": "17" } }, + { + "name": "base-clion", + "description": "Single-config Ninja for CLion", + "generator": "Ninja", + "hidden": true, + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_CXX_STANDARD": "17" + } + }, { "name": "base-vs19", "description": "Base configuration using Visual Studio 16 (2019)", @@ -145,6 +155,62 @@ "CMAKE_CXX_FLAGS_DEBUG": "-O1" } }, + { + "name": "rigorous", + "hidden": true, + "cacheVariables": { + "CCMATH_BUILD_TESTS": "ON", + "CCMATH_BUILD_SIMPLE_TESTS": "OFF", + "CCMATH_BUILD_RIGOROUS_TESTS": "ON", + "CCMATH_ENABLE_MPFR_TESTS": "ON", + "CCMATH_ENABLE_SEARCH_TOOLS": "ON", + "CCMATH_DESIRED_CXX_STANDARD": "cxx_std_20", + "CCMATH_ENABLE_AGGRESSIVE_WARNINGS": "ON", + "CCMATH_ENABLE_WARNINGS_AS_ERRORS": "ON" + } + }, + { + "name": "rigorous-coremath", + "hidden": true, + "cacheVariables": { + "CCMATH_ENABLE_COREMATH_TESTS": "ON", + "CCMATH_COREMATH_ROOT": "${sourceDir}/coremath-prefix" + } + }, + { + "name": "showcase", + "hidden": true, + "cacheVariables": { + "CCMATH_BUILD_SHOWCASE": "ON", + "CCMATH_BUILD_BENCHMARKS": "ON", + "CCMATH_BUILD_TESTS": "OFF", + "CCMATH_SHOWCASE_ENABLE_GCEM": "ON", + "CCMATH_SHOWCASE_ENABLE_MPFR": "ON", + "CCMATH_DESIRED_CXX_STANDARD": "cxx_std_20" + } + }, + { + "name": "examples", + "hidden": true, + "cacheVariables": { + "CCMATH_BUILD_EXAMPLES": "ON", + "CCMATH_BUILD_TESTS": "OFF", + "CCMATH_BUILD_BENCHMARKS": "OFF", + "CCMATH_BUILD_SHOWCASE": "OFF" + } + }, + { + "name": "rigorous-vcpkg", + "hidden": true, + "environment": { + "VCPKG_ROOT": "${sourceDir}/vcpkg" + }, + "cacheVariables": { + "CCMATH_ENABLE_COREMATH_TESTS": "OFF", + "VCPKG_MANIFEST_DIR": "${sourceDir}/cmake/vcpkg", + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + } + }, { "name": "ninja-gcc-debug", "inherits": [ @@ -182,6 +248,86 @@ ], "description": "Ninja Multi-config + Clang (Debug)" }, + { + "name": "clion-clang-debug", + "displayName": "CLion Clang Debug", + "inherits": [ + "base-clion", + "clang", + "Debug" + ], + "binaryDir": "${sourceDir}/out/clang", + "description": "Single-config Ninja + Clang (Debug) for CLion" + }, + { + "name": "clion-clang-debug-examples", + "displayName": "CLion Clang Debug + examples", + "inherits": [ + "base-clion", + "clang", + "Debug", + "examples" + ], + "binaryDir": "${sourceDir}/out/clang-examples", + "description": "Single-config Ninja + Clang (Debug) for CLion examples" + }, + { + "name": "clion-clang-debug-rigorous-dev", + "displayName": "CLion Clang Debug + rigorous", + "inherits": [ + "base-clion", + "clang", + "Debug", + "rigorous" + ], + "binaryDir": "${sourceDir}/out/clang-rigorous-dev", + "description": "CLion Clang Debug with simple and rigorous test suites", + "cacheVariables": { + "CCMATH_BUILD_SIMPLE_TESTS": "ON", + "CCMATH_ENABLE_AGGRESSIVE_WARNINGS": "OFF", + "CCMATH_ENABLE_WARNINGS_AS_ERRORS": "OFF", + "CCMATH_ENABLE_COREMATH_TESTS": "OFF" + } + }, + { + "name": "clion-clang-release-showcase", + "displayName": "CLion Clang Release + showcase", + "inherits": [ + "base-clion", + "clang", + "Release", + "showcase" + ], + "binaryDir": "${sourceDir}/out/clang-showcase", + "description": "Single-config Release showcase comparisons for CLion" + }, + { + "name": "ninja-clang-debug-rigorous-dev", + "inherits": [ + "base", + "clang", + "Debug", + "rigorous", + "rigorous-coremath" + ], + "description": "Clang Debug with simple and rigorous test suites for CLion", + "cacheVariables": { + "CCMATH_BUILD_SIMPLE_TESTS": "ON", + "CCMATH_ENABLE_AGGRESSIVE_WARNINGS": "OFF", + "CCMATH_ENABLE_WARNINGS_AS_ERRORS": "OFF" + } + }, + { + "name": "ninja-clang-debug-rigorous", + "inherits": [ + "base", + "clang", + "Debug", + "rigorous", + "rigorous-coremath" + ], + "description": "Clang Debug for rigorous validation only" + }, { "name": "ninja-fuzz-debug", "inherits": [ @@ -200,6 +346,38 @@ ], "description": "Ninja Multi-config + Clang (Release)" }, + { + "name": "ninja-clang-release-rigorous", + "inherits": [ + "base", + "clang", + "Release", + "rigorous", + "rigorous-coremath" + ], + "description": "Ninja Multi-config + Clang (Release) for rigorous validation" + }, + { + "name": "ninja-clang-release-showcase", + "inherits": [ + "base", + "clang", + "Release", + "showcase" + ], + "description": "Ninja Multi-config + Clang (Release) for library showcase comparisons" + }, + { + "name": "ninja-gcc-release-rigorous", + "inherits": [ + "base", + "gcc", + "Release", + "rigorous", + "rigorous-coremath" + ], + "description": "Ninja Multi-config + GCC (Release) for rigorous validation" + }, { "name": "ninja-clang-minsizerel", "inherits": [ @@ -259,6 +437,17 @@ ], "description": "VS2022 + Release" }, + { + "name": "vs22-release-rigorous", + "inherits": [ + "base-vs22", + "Release", + "rigorous", + "rigorous-vcpkg" + ], + "description": "VS2022 + Release for rigorous MPFR validation via vcpkg", + "binaryDir": "${sourceDir}/out/build/vs22/vs22-release-rigorous" + }, { "name": "vs22-clang-debug", "inherits": [ @@ -302,6 +491,52 @@ "all" ] }, + { + "name": "build-clion-clang-debug", + "configurePreset": "clion-clang-debug", + "targets": [ + "all" + ] + }, + { + "name": "build-clion-clang-debug-examples", + "configurePreset": "clion-clang-debug-examples", + "targets": [ + "ccmath-example-all" + ] + }, + { + "name": "build-clion-clang-debug-rigorous-dev", + "configurePreset": "clion-clang-debug-rigorous-dev", + "targets": [ + "all" + ] + }, + { + "name": "build-clion-clang-release-showcase", + "configurePreset": "clion-clang-release-showcase", + "targets": [ + "showcase_bench_sqrt", + "showcase_bench_sin", + "showcase_accuracy" + ] + }, + { + "name": "build-ninja-clang-debug-rigorous-dev", + "configurePreset": "ninja-clang-debug-rigorous-dev", + "configuration": "Debug", + "targets": [ + "all" + ] + }, + { + "name": "build-ninja-clang-debug-rigorous", + "configurePreset": "ninja-clang-debug-rigorous", + "configuration": "Debug", + "targets": [ + "all" + ] + }, { "name": "build-ninja-fuzz-debug", "configurePreset": "ninja-fuzz-debug", @@ -318,6 +553,32 @@ "all" ] }, + { + "name": "build-ninja-clang-release-rigorous", + "configurePreset": "ninja-clang-release-rigorous", + "configuration": "Release", + "targets": [ + "all" + ] + }, + { + "name": "build-ninja-clang-release-showcase", + "configurePreset": "ninja-clang-release-showcase", + "configuration": "Release", + "targets": [ + "showcase_bench_sqrt", + "showcase_bench_sin", + "showcase_accuracy" + ] + }, + { + "name": "build-ninja-gcc-release-rigorous", + "configurePreset": "ninja-gcc-release-rigorous", + "configuration": "Release", + "targets": [ + "all" + ] + }, { "name": "build-ninja-clang-minsizerel", "configurePreset": "ninja-clang-minsizerel", @@ -374,6 +635,14 @@ "ALL_BUILD" ] }, + { + "name": "build-vs22-release-rigorous", + "configurePreset": "vs22-release-rigorous", + "configuration": "Release", + "targets": [ + "ALL_BUILD" + ] + }, { "name": "build-vs22-clang-debug", "configurePreset": "vs22-clang-debug", @@ -408,6 +677,55 @@ "configuration": "Debug", "inheritConfigureEnvironment": true }, + { + "name": "test-clion-clang-debug", + "configurePreset": "clion-clang-debug", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "simple" + } + } + }, + { + "name": "test-clion-clang-debug-rigorous-dev", + "configurePreset": "clion-clang-debug-rigorous-dev", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "rigorous" + } + } + }, + { + "name": "test-ninja-clang-debug-rigorous-dev", + "configurePreset": "ninja-clang-debug-rigorous-dev", + "configuration": "Debug", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + } + }, + { + "name": "test-ninja-clang-debug-rigorous", + "configurePreset": "ninja-clang-debug-rigorous", + "configuration": "Debug", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "rigorous" + } + } + }, { "name": "test-ninja-fuzz-debug", "configurePreset": "ninja-fuzz-debug", @@ -425,6 +743,34 @@ "configuration": "Release", "inheritConfigureEnvironment": true }, + { + "name": "test-ninja-clang-release-rigorous", + "configurePreset": "ninja-clang-release-rigorous", + "configuration": "Release", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "rigorous" + } + } + }, + { + "name": "test-ninja-gcc-release-rigorous", + "configurePreset": "ninja-gcc-release-rigorous", + "configuration": "Release", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "rigorous" + } + } + }, { "name": "test-ninja-clang-minsizerel", "configurePreset": "ninja-clang-minsizerel", @@ -467,11 +813,152 @@ "configuration": "Release", "inheritConfigureEnvironment": true }, + { + "name": "test-vs22-release-rigorous", + "configurePreset": "vs22-release-rigorous", + "configuration": "Release", + "inheritConfigureEnvironment": true, + "output": { + "outputOnFailure": true + }, + "filter": { + "include": { + "label": "rigorous" + } + } + }, { "name": "test-vs22-clang-debug", "configurePreset": "vs22-clang-debug", "configuration": "Debug", "inheritConfigureEnvironment": true } + ], + "workflowPresets": [ + { + "name": "workflow-clion-clang-debug-examples", + "displayName": "CLion examples", + "description": "Configure and build the example targets for CLion", + "steps": [ + { + "type": "configure", + "name": "clion-clang-debug-examples" + }, + { + "type": "build", + "name": "build-clion-clang-debug-examples" + } + ] + }, + { + "name": "workflow-clion-clang-debug-rigorous-dev", + "displayName": "CLion rigorous validation", + "description": "Configure, build, and run rigorous ctest for CLion", + "steps": [ + { + "type": "configure", + "name": "clion-clang-debug-rigorous-dev" + }, + { + "type": "build", + "name": "build-clion-clang-debug-rigorous-dev" + }, + { + "type": "test", + "name": "test-clion-clang-debug-rigorous-dev" + } + ] + }, + { + "name": "workflow-clion-clang-release-showcase", + "displayName": "CLion showcase", + "description": "Configure and build showcase comparison targets for CLion", + "steps": [ + { + "type": "configure", + "name": "clion-clang-release-showcase" + }, + { + "type": "build", + "name": "build-clion-clang-release-showcase" + } + ] + }, + { + "name": "workflow-ninja-clang-debug-rigorous-dev", + "displayName": "Dev + rigorous (Clang Debug)", + "description": "Configure, build, and run all ctest suites for CLion development", + "steps": [ + { + "type": "configure", + "name": "ninja-clang-debug-rigorous-dev" + }, + { + "type": "build", + "name": "build-ninja-clang-debug-rigorous-dev" + }, + { + "type": "test", + "name": "test-ninja-clang-debug-rigorous-dev" + } + ] + }, + { + "name": "workflow-ninja-clang-release-rigorous", + "displayName": "Rigorous validation (Clang Release)", + "description": "Configure, build, and run the rigorous ctest suite", + "steps": [ + { + "type": "configure", + "name": "ninja-clang-release-rigorous" + }, + { + "type": "build", + "name": "build-ninja-clang-release-rigorous" + }, + { + "type": "test", + "name": "test-ninja-clang-release-rigorous" + } + ] + }, + { + "name": "workflow-ninja-gcc-release-rigorous", + "displayName": "Rigorous validation (GCC Release)", + "description": "Configure, build, and run the rigorous ctest suite", + "steps": [ + { + "type": "configure", + "name": "ninja-gcc-release-rigorous" + }, + { + "type": "build", + "name": "build-ninja-gcc-release-rigorous" + }, + { + "type": "test", + "name": "test-ninja-gcc-release-rigorous" + } + ] + }, + { + "name": "workflow-vs22-release-rigorous", + "displayName": "Rigorous validation (VS2022 Release)", + "description": "Configure, build, and run the rigorous MPFR ctest suite via vcpkg", + "steps": [ + { + "type": "configure", + "name": "vs22-release-rigorous" + }, + { + "type": "build", + "name": "build-vs22-release-rigorous" + }, + { + "type": "test", + "name": "test-vs22-release-rigorous" + } + ] + } ] } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 640fb772..62e8566a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,225 +1,215 @@ -# Contribution Guidelines - -> [!IMPORTANT] -> Currently the contribution guidelines are a work in progress. Please check back later for more information. - # Contributing to CCMath -First off, thanks for taking the time to contribute! ❤️ - -All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 - -If you enjoy this project but don’t have the time or ability to contribute code, you can still show your support in the following ways: -- Star the repository -- Mention this project in your own project's documentation -- Share this project on social media or at community meetups - - -## Table of Contents - -- [I Have a Question](#i-have-a-question) -- [I Want To Contribute](#i-want-to-contribute) - - [Core Tenets of CCMath](#core-tenets-of-ccmath) - - [Your First Code Contribution](#your-first-code-contribution) -- [How to manually build CCMath with the command line](#how-to-manually-build-ccmath-with-the-command-line) - - [Prerequisites](#prerequisites) - - [How to clone project from GitHub](#how-to-clone-project-from-github) - - [How to build the project](#how-to-build-the-project) - - [How to run tests](#how-to-run-tests) -- [Additional Notes](#additional-notes) - - -## I Have a Question - -> If you want to ask a question, we assume that you have read the available [Documentation](). - -Before you ask a question, it is best to search for existing [Issues](https://github.com/Rinzii/ccmath/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. - -If you then still feel the need to ask a question and need clarification, we recommend the following: - -- Open an [Issue](https://github.com/Rinzii/ccmath/issues/new). -- Provide as much context as you can about what you're running into. -- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. - -We will then take care of the issue as soon as possible. - - -## I Want To Contribute - -> **Legal Notice** -> By contributing to this project, you agree that: -> - You have authored 100% of the content, or the content is compatible with the project’s license and is marked as such -> - You have the rights necessary to share the content -> - The content may be provided under the project’s license - -***Before any code can be written, consider these core tenets of CCMath as you work on the project.*** - -### Core Tenets of CCMath +For help understanding how to implement elementary functions in CCMath, see +[Approximating Functions: A Practical Guide to Sollya and Function Implementation](docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf). + +PRs go to the `main` branch. Bug reports and design questions belong in +[Issues](https://github.com/Rinzii/ccmath/issues). + +By contributing you confirm the change is yours to license and compatible with +Apache-2.0 WITH LLVM-exception. + +## Project layout + +A quick map of the repository: + +- `include/ccmath/` is the header-only library. Each function sits under its family + (`math/power/`, `math/trig/`, and so on), and the internals, including the + `builtin`, `gen`, and `rt` kernels, sit under `include/ccmath/internal/`. +- `tests/` holds the test suites, with the ULP and conformance harnesses under + `tests/shared/utils` and the MPFR oracles under `tests/shared/oracle`. +- `docs/` has the longer guides, and `cmake/`, `CMakeLists.txt`, and `CMakePresets.json` + drive the build. + +## Core tenets + +1. Always follow the C and C++ standard requirements for ``. Match the specified semantics, domain and range + handling, special values, and error reporting (errno and the floating-point exception flags). +2. Aim for a correctly rounded result in all four rounding modes, with documented compromises up to 4 ULP or fewer modes + allowed to ship sooner. See [Accuracy and rounding goals](#accuracy-and-rounding-goals). +3. Prefer constexpr evaluation where the standard allows it (`gen` kernels, `static_assert` smoke). +4. Keep runtime performance in the same ballpark as the platform libm on the cases it covers. +5. Follow the existing dispatch layout (`builtin` / `gen` / `rt`, or impl-direct where the family already does). +6. Prefer portable generic kernels over platform forks when both satisfy the standard. +7. Mirror supported compiler behavior where the standard leaves details unspecified (for example `fpclassify` payload + bits). +8. Add tests in the same change. Cover standards-mandated edge cases and ULP checks on public APIs. +9. Doxygen on exported entry points (`@brief`, `@param`, `@return`). +10. Keep the public header layout and naming conventions aligned with [cppreference + ``](https://en.cppreference.com/w/cpp/header/cmath). + +## Accuracy and rounding goals + +The end goal for every function is a correctly rounded result, bit-for-bit the +infinitely precise value rounded to the destination type, in all four IEEE +rounding modes (`FE_TONEAREST`, `FE_DOWNWARD`, `FE_UPWARD`, `FE_TOWARDZERO`). That +is the bar the library is held to. + +We accept documented compromises that ship in-contract functionality +sooner: + +- A function that is not yet correctly rounded may ship with a stated, tested + error bound of no more than 4 ULP. The ULP suites under `tests/shared/utils` + check against the platform libm, and an MPFR oracle checks against the correctly + rounded value where one is targeted. +- A function may support fewer than the four rounding modes. Shipping + `FE_TONEAREST` first, with the directed modes to follow, is fine. + +A compromise only counts if it is written down. State the current ULP bound and +the supported rounding modes at the function, say what is missing, and leave a +`TODO:` plus a tracking issue so the gap stays visible. An undocumented one +is a bug. A documented one is a known limitation we can close later. + +Accuracy and standards conformance are the top priority. Performance is the next +priority after that. Once a function is correct it should stay in the same ballpark +as the platform libm where it is implemented, and the usual shape is a fast common +case backed by an accurate fallback rather than one slow implementation. When the +two genuinely conflict, correctness wins, but reach for a layered design before +trading away speed. + +## Build from the command line -1. Code should be evaluable at compile time when possible and great effort should be taken to comply with this (e.g., `constexpr` and `static_assert`). -2. Match the behavior of the standard library as closely as possible, with no compromises on accuracy. -3. Strive for performance comparable to (or no worse than half the speed of) the standard library. -4. Ensure code can work with `static_assert`. -5. Favor generic solutions over platform-specific ones, when possible. -6. Mimic the behavior of compilers we support (particularly relevant for `ccm::fpclassify`). -7. Provide comprehensive tests covering all edge cases for any new code. -8. Document thoroughly with Doxygen, ensuring clarity and completeness. -9. Follow the same API layout as the standard library. Refer to [cppreference](https://en.cppreference.com/w/cpp/header/cmath) for guidance on `'s api. - -### Your First Code Contribution - -If you’re ready to make your first contribution to CCMath, here’s a basic workflow you can follow: - -1. **Fork and Clone** - - Fork the CCMath repository on GitHub to your personal account. - - Clone your fork locally: - ```bash - git clone https://github.com//ccmath.git - cd ccmath - ``` - -2. **Check Out the `dev` Branch** - - We recommend making changes off of the `dev` branch: - ```bash - git checkout dev - ``` - -3. **Set Up Your Environment** - - Make sure you have the prerequisites installed (CMake, Ninja, Git, and a compatible compiler). - - Install any other dependencies specific to your platform (for example, using `apt`, `brew`, or `choco`). - -4. **Create a Feature Branch** - - Create a new branch for your proposed changes: - ```bash - git checkout -b feature/my-new-feature - ``` - -5. **Implement Your Changes** - - Edit or add source/header files under `ccmath/`. - - Remember the [Core Tenets of CCMath](#core-tenets-of-ccmath)—aim for constexpr-friendly, standards-compliant, well-documented, and well-tested code. - -6. **Build and Test Locally** - - Use the provided CMake Presets to configure, build, and test: - ```bash - cmake --preset=ninja-gcc-debug - cmake --build --preset=build-ninja-gcc-debug - ctest --preset=test-ninja-gcc-debug --output-on-failure - ``` - - If you encounter any build or test failures, consult the project’s [Issues](https://github.com/Rinzii/ccmath/issues) or open a new one if needed. - -7. **Document Your Code** - - Add or update Doxygen comments where appropriate to ensure comprehensive coverage of new or modified functions. - - If applicable, include references or code examples in docstrings. +### Prerequisites -8. **Commit and Push** - - Once your changes pass locally, commit them with a meaningful message: - ```bash - git add . - git commit -m "Implement new math function with tests" - ``` - - Push your branch to your fork: - ```bash - git push -u origin feature/my-new-feature - ``` +- CMake 3.18+ +- Git +- Ninja (Linux and macOS) +- Clang or GCC (Linux and macOS) +- Visual Studio 2022 (Windows) -9. **Open a Pull Request** - - On GitHub, open a Pull Request (PR) from your branch to `Rinzii/ccmath`’s `dev` branch. - - Provide a clear title and description of your changes. - - Describe any related issue references (if applicable) and how you tested your changes. +### Linux and macOS -10. **Code Review** -- A maintainer will review your PR. If changes are requested, address them and push updates to the same feature branch. -- Once everything is approved, your PR will be merged into `dev`. +```bash +git clone https://github.com/Rinzii/ccmath.git +cd ccmath +git checkout main +cmake --preset=ninja-gcc-debug +cmake --build --preset=build-ninja-gcc-debug +ctest --preset=test-ninja-gcc-debug --output-on-failure +``` -That’s it! Thank you for contributing to CCMath. If you have any questions or run into any problems, don’t hesitate to open an issue or add a comment in your PR. We appreciate your help in making CCMath a robust, efficient, and user-friendly math library. +Other configure presets are listed in `CMakePresets.json` (`ninja-clang-debug`, +`ninja-clang-release`, and similar). Pass `-DCMAKE_CXX_STANDARD=17` or `20` on the +configure line when you need a specific standard. +### Windows (Visual Studio 2022) -## How to manually build CCMath with the command line +```powershell +git clone https://github.com/Rinzii/ccmath.git +cd ccmath +git checkout main +cmake --preset=vs22-debug +cmake --build --preset=build-vs22-debug +ctest --preset=test-vs22-debug --output-on-failure +``` -### Prerequisites -- [CMake 3.18+](https://cmake.org/download/) -- [Git](https://git-scm.com/) -- [Ninja](https://ninja-build.org/) (for Linux & macOS -- [Clang](https://clang.llvm.org/) or [GCC](https://gcc.gnu.org/) (for Linux & macOS) -- [Visual Studio 22](https://visualstudio.microsoft.com/) (for Windows) +Preset names vary. Read `CMakePresets.json` before assuming a name exists locally. -### How to clone project from GitHub +## First PR -Step 1: Clone the repository -```bash -git clone https://github.com/Rinzii/ccmath.git -``` +Fork the repo, then branch off `main`: -Step 2: (recommended) Check out the dev branch ```bash -git checkout dev +git clone https://github.com//ccmath.git +cd ccmath +git checkout main +git checkout -b ``` -## Building on Linux & macOS - -1. Pick a preset for your compiler and configuration. - - Examples (see `CMakePresets.json` for the full list): - - `ninja-gcc-debug` - - `ninja-gcc-release` - - `ninja-clang-debug` - - `ninja-clang-release` - - `ninja-clang-libcpp-relwithdebinfo` - - If you need a specific C++ standard, pass `-DCMAKE_CXX_STANDARD=17` (or 20, etc.) after specifying the preset. -2. Configure using one of the presets (example shown uses GCC Debug with C++17): - ```bash - cmake --preset=ninja-gcc-debug -DCMAKE_CXX_STANDARD=17 - ``` -3. Build the project - ```bash - cmake --preset=ninja-gcc-debug -DCMAKE_CXX_STANDARD=17 - ``` - Or, if you prefer a single step after configuring: - ```bash - cmake --preset=ninja-gcc-debug -DCMAKE_CXX_STANDARD=17 - ``` - You can also specify `--config Debug` or `--config Release` if needed -4. Run the tests - ```bash - ctest --preset=test-ninja-gcc-debug --output-on-failure - ``` - Or, if you prefer a one-liner after configuring and building: - ```bash - ctest -C Debug --output-on-failure - ``` - (Ensure you built a Debug config if you’re testing Debug.) - -## Building on Windows (Visual Studio 2022) - -1. Choose a preset, for example: - - `vs22-debug` - - `vs22-release` - - `vs22-clang-debug` - -2. Configure: - ```powershell - cmake --preset=vs22-debug -DCMAKE_CXX_STANDARD=17 - ``` -3. Build using the corresponding build preset: - ```powershell - cmake --build --preset=vs22-debug - ``` - Or, if you prefer a single step after configuring: - ```powershell - # In the same directory where you ran cmake --preset=... - cmake --build . --config Debug - ``` -4. Run the tests - ```powershell - ctest --preset=test-vs22-debug --output-on-failure - ``` - Or, if you prefer a one-liner after configuring and building: - ```powershell - ctest -C Debug --output-on-failure - ``` - -## Additional Notes - -- The exact preset names may differ in your local `CMakePresets.json`. Check that file for the list of available presets. -- Presets for coverage, sanitizer builds, or specialized compilers like Intel may also be available (for example, `ninja-intel-relwithdebinfo` or `ninja-gcc-ccache-debug`). -- If you run into issues, confirm that your CMake, Ninja, and compiler versions are current and supported. -- See our [Contribution Guidelines](CONTRIBUTING.md) for more detailed information on contributing and troubleshooting. +Build and test with the presets above. Implementation work stays under +`include/ccmath/`, follows the patterns already present in the function family you +touch, and does not add `` includes. When the change is ready, open a PR +against `main` as described in [Opening a pull request](#opening-a-pull-request). + +## Issue and pull request workflow + +### Branches + +`main` is the development trunk and the target for almost all PRs. `release/N.x` +branches carry stabilization for a given minor series. Open fixes against `main` +first. A maintainer backports to a release branch when it applies. Never +force-push `main`. + +### Releases and tags + +Releases are annotated tags `vMAJOR.MINOR.PATCH`, with release candidates as +`vMAJOR.MINOR.PATCH-rc.N`. Below `1.0`, a minor bump may carry breaking API changes. + +### Filing an issue + +Bug reports use the form under `.github/ISSUE_TEMPLATE`. For a numeric defect, +give the exact function, the affected evaluation (constexpr, runtime, SIMD, or +deterministic mode), the scalar type, and the exact input, expected, and actual +values. Hex float literals such as `0x1.0p+2` avoid precision loss. State the +active rounding mode and the source of truth for the expected result (MPFR, the +C++ standard, or a reference libm). + +### Agree on the approach first + +Hard numeric work gets a design review on the issue before you implement it. For a +correctly-rounded function, or work on accuracy and ULP, rounding modes, or range +reduction, comment on the issue with your intended approach and the exact inputs you +plan to validate against, and wait for a maintainer to sign off before opening a +non-draft PR. A draft PR holding only the acceptance tests and a validation plan is a +good way to start that conversation. The point is to catch a wrong method in a short +comment rather than in a finished kernel, so you do not pour effort into an approach +that has to be redone. Likewise, hold off on implementing a new feature until a +maintainer approves the request. + +### Opening a pull request + +PRs target `main` unless the fix is specific to a release branch. Keep the change +minimal and aligned with the function family you touch. Use a one-line imperative +subject with no trailing period. Open the PR from a topic branch, since a PR from +your fork's `main`, `master`, or `develop` is closed automatically. + +Before you request review, read your own diff once outside the editor, make sure +the warning-as-error build and the test presets are green, and write a short +summary of what changed and why. Small focused PRs get reviewed faster and more +carefully than large ones, so split unrelated work and leave drive-by refactors +to their own change. Once review starts, answer every comment, even if only to +say it is done. When a reviewer misreads the code, prefer making the code or a +why comment clearer over explaining only in the thread, since the next reader +sees the code and not the discussion. + +### Cite your sources + +Most numeric work builds on existing research, so share what you drew on. In the +issue or PR, name the algorithm and the reference behind it, whether that is a paper, +a worked derivation, or a reference implementation you studied for its behavior. Link +any analysis or proof artifact too, a Sollya script, a Gappa proof, or the MPFR +comparison you validated against. A reviewer can check a method far faster when its +source is named than when they have to reconstruct it from the code, and a future +maintainer can follow the reasoning rather than guess at it. + +Also, if any code is adapted from another source rather than written from scratch, name +that source in a comment on the code itself, not only in the PR. Only adapt from a source +whose license is compatible with Apache-2.0 WITH LLVM-exception. + +### Testing math changes + +Tests go in the same change as the code. A few specifics matter for math +kernels and are easy to get wrong: + +- Exercise the code you changed. A public call such as `ccm::pow` can resolve to + a compiler builtin at runtime, so a plain runtime test may never reach the + ccmath kernel. Drive the kernel through a `constexpr` context (a + `static_assert`) or by calling the generic kernel directly (`ccm::gen::*_gen`). +- Validate numeric changes against an oracle. Use MPFR, Sollya, or Gappa and note + the source of truth rather than eyeballing a value or trusting a system libm at + a boundary. The MPFR oracle harnesses under `tests/shared/oracle` are the + pattern to follow. +- Test boundaries, not a single point. Cover overflow, underflow, subnormals, and + signed zero, along with the worst-case inputs for the function, not just one + representative value. +- Cover every type the function is instantiated for, `float` and `double` at a + minimum. +- For exception or errno behavior, use the fenv fixture at + `tests/shared/utils/fenv_fixture.hpp`. The ULP and conformance suites live under + `tests/shared/utils`. + +## More detail + +- [CONVENTIONS.md](CONVENTIONS.md) for the code-level conventions +- [docs/STATUS.md](docs/STATUS.md) for module completion +- [SECURITY.md](SECURITY.md) for vulnerability reports diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 00000000..c6b6b1b6 --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,184 @@ +# CCMath conventions + +The code-level companion to [CONTRIBUTING.md](CONTRIBUTING.md), which has the tenets, +accuracy policy, and PR workflow. These are the patterns you will see across the +headers, and the ones easy to get wrong. + +## Project layout + +```mermaid +graph LR + A["include/ccmath/"] --> B["ccmath.hpp (umbrella header)"] + A --> C["math/ (public headers, per family)"] + A --> D["internal/ (private, may change any commit)"] + D --> D1["config/ (CCM_CONFIG_* flags)"] + D --> D2["predef/ (compiler, platform wrappers)"] + D --> D3["support/ (fp, fenv, helpers)"] + D --> D4["types/ (internal aliases)"] + D --> D5["math/"] + D5 --> K1["generic/builtins/ : ccm::builtin"] + D5 --> K2["generic/func/ : ccm::gen (*_gen)"] + D5 --> K3["runtime/func/ : ccm::rt (*_rt)"] +``` + +Public headers live under `math/`, one per function by family. Everything else is +private under `internal/`. Families: `basic`, `compare`, `expo`, `fmanip`, `hyper`, +`misc`, `nearest`, `power`, `special`, `trig`, matching +[cppreference ``](https://en.cppreference.com/w/cpp/header/cmath). The key part +is the three-kernel split at the bottom: nearly every function has a `builtin`, a +`gen`, and an `rt` kernel, and which runs depends on where you call it from. + +## Dispatch + +The public header owns the choice, and you should not add layers between it and the +kernels. Every public `ccm` function is `constexpr` and routes on one question: is +this a constant expression? + +```mermaid +flowchart TD + A["ccm::fn(...) public, constexpr"] --> B{"is_constant_evaluated()?"} + B -- "compile time" --> C{"constexpr builtin available?"} + C -- "yes" --> D["ccm::builtin::*_ct"] + C -- "no" --> E["ccm::gen::*_gen"] + B -- "runtime" --> F["ccm::rt::*_rt"] +``` + +At compile time, use the constexpr builtin if one exists (constant evaluation rounds +to nearest, so it is correct), otherwise the generic kernel. At runtime, always `rt`, +since a builtin lowers to libm and we trust it only under `FE_TONEAREST`. That is also +why each non-generic runtime kernel sits in its own `*_rt.hpp` and guards the rounding +mode. `include/ccmath/math/power/pow.hpp` is the shape to copy: + +```cpp +template , bool> = true> +constexpr T pow(T base, T exp) +{ + if constexpr (ccm::builtin::has_constexpr_pow) + { + if (support::is_constant_evaluated()) { return ccm::builtin::pow_ct(base, exp); } + return rt::pow_rt(base, exp); + } + else + { + if (support::is_constant_evaluated()) { return gen::pow_gen(base, exp); } + return rt::pow_rt(base, exp); + } +} +``` + +## Standards conformance + +CCMath is a drop-in ``: observable behavior matches what the C and C++ +standards specify, so swapping `std::` for `ccm::` changes no result. That is why the +edge cases get the same care as ordinary inputs. + +The arithmetic overloads apply the [c.math]/3 promotion rule, promoting mixed inputs +to the common floating type and integers to `double`, so `ccm::pow(2, 3.0)` matches +the standard call (`pow.hpp` keeps this in `detail::cmath_pow_result_t`). Special +values follow IEEE 754 and Annex F: NaN propagation, the standard-pinned sign of zero, +and the infinities. Where the standard leaves a result implementation-defined (some +`fmod` NaN cases, the `fpclassify` payload bits), we match the supported compilers +rather than invent one. + +Errors follow the C `math_errhandling` model, `errno` plus a floating-point exception +flag, so existing error checks keep working (mechanics under +[Errors and rounding](#errors-and-rounding)). Recent C++ revisions also made `` +usable in constant expressions, which is what CCMath already does, so there is no +separate constexpr API. + +## Namespaces + +The public API is `ccm`, names matching ``. The three dispatch layers each get +a namespace, and the rest is internal support. + +| Namespace | Role | Public | +|--------------------------------------------------------------|-----------------------------------------------------------|--------| +| `ccm` | public API, names match `` (`pow`, `powf`, `powl`) | yes | +| `ccm::builtin` | compiler-builtin wrappers (`*_ct`) | no | +| `ccm::gen` | generic kernels (`*_gen`) | no | +| `ccm::rt` | runtime kernels (`*_rt`) | no | +| `ccm::detail` | trait and overload glue local to a public header | no | +| `ccm::support` (`::fp`, `::fenv`, `::helpers`), `ccm::types` | internal utilities | no | + +If it is not in `ccm` proper, it is not API and can change in any commit. + +## Naming and macros + +Names follow `` and the surrounding code. Functions are snake_case (`pow`, +`pow_gen`), types PascalCase (`FPBits`, `NormalizedFloat`), and aliases snake_case +with a `_t` suffix (`fp_bits_t`, `half_width_t`). Template parameters are `T` for the +primary type, descriptive PascalCase otherwise (`Integer`, `Real`). + +Macros use `CCMATH_` for public, versioning, and compiler config, and `CCM_` for +internal use and the `CCM_CONFIG_*` flags. No other prefixes. Leave a `TODO:` only +when a gap genuinely cannot be closed in the change. + +## Support utilities + +Check `ccm::support` before hand-rolling anything low-level. `FPBits` (`fp_bits_t`) +takes a float apart into sign, exponent, and mantissa, and builds specials like +`quiet_nan()`, so a raw `bit_cast` is rarely what you want. Use +`ccm::support::is_constant_evaluated()` for the compile-time branch, not the `std::` +one, since C++17 is the floor. Fused multiply-add and the error-free transforms on it +live in `ccm::support::multiply_add`. + +## Errors and rounding + +CCMath never throws. A domain, pole, or range error sets `errno` and raises the +matching floating-point exception flag through the `ccm::support::fenv` helpers +(`set_errno_if_required`, `raise_except_if_required`), which are config-guarded and +stay quiet during constant evaluation. Report errors that way, not with a C++ +exception. + +Kernels honor all four IEEE rounding modes at runtime, so read the active mode and +adjust the bit work or the final rounding around it. Never call `fesetround` inside a +kernel, and do not use `#pragma STDC FENV_ACCESS`. The accuracy bar and the +documented-compromise policy are in +[CONTRIBUTING.md](CONTRIBUTING.md#accuracy-and-rounding-goals). + +## File conventions + +Each header opens with the SPDX license block (Apache-2.0 WITH LLVM-exception) and +`#pragma once`. Project headers come first, as quoted includes from the include +root (`"ccmath/..."`), then standard headers in angle brackets. No `` +under `include/`: CCMath is the `` here, so use the internal `builtin`, `gen`, +and `rt` kernels. + +## C++ usage + +C++17 is the minimum, so gate any newer feature on the standard it needs, not on what +a given compiler accepts. Casts are C++-style only (`static_cast`, `reinterpret_cast`, +`const_cast`, `bit_cast`), never the C form. Otherwise it is ordinary modern C++: the +`_v` and `_t` aliases, `enum class`, `using` over `typedef`, and brace initialization. + +Two ccmath specifics. `[[nodiscard]]` goes on internal helpers and accessors, not the +public entry points, which mirror the unmarked `` signatures. Compiler and +platform checks go through the `internal/predef` wrappers (`has_builtin`, +`has_attribute`, `likely`, `unlikely`, `assume`), not raw `__GNUC__` or `_MSC_VER`. +Everything builds clean under `-Werror` with aggressive warnings. + +## Comments + +Public entry points get Doxygen: `@brief`, `@param` or `@tparam`, and `@return`. +Implementation comments are for the why, not the what: the constraint, the tradeoff, +the reason a builtin is or is not trusted. `pow.hpp` is a good model. + +## Formatting + +clang-format is authoritative. Run `lint.sh` or `lint.bat` (which also runs +clang-tidy) before pushing. The settings worth knowing: + +| Setting | Value | +|-----------------------|------------------| +| Base style | LLVM | +| Braces | Allman | +| Indent | tabs, width 4 | +| Column limit | 160 | +| Namespace indentation | all | +| Pointer alignment | middle (`T * x`) | + +## Build systems + +CMake is the primary build. Meson and Premake are self-contained for consumers and +need no CMake configure first. Library options are declared once in +`cmake/config/BuildManifest.cmake` and flow to the secondary builds from there. diff --git a/README.md b/README.md index 6e0b99d0..fb098852 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,259 @@ CCMath Logo -# CCMath - A Modern, Compile-Time `` Alternative +# CCMath -[![image](https://github.com/Rinzii/ccmath/workflows/ci-windows/badge.svg)](https://github.com/Rinzii/ccmath/actions?query=workflow%3Aci-windows) -[![image](https://github.com/Rinzii/ccmath/workflows/ci-linux/badge.svg)](https://github.com/Rinzii/ccmath/actions?query=workflow%3Aci-linux) -[![image](https://github.com/Rinzii/ccmath/workflows/ci-macos/badge.svg)](https://github.com/Rinzii/ccmath/actions?query=workflow%3Aci-macos) -[![image](https://api.securityscorecards.dev/projects/github.com/Rinzii/ccmath/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Rinzii/ccmath) +**A constexpr-first `` for C++17 and later** + +[![Release](https://img.shields.io/github/v/release/Rinzii/ccmath?sort=semver&label=release)](https://github.com/Rinzii/ccmath/releases/latest) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Rinzii/ccmath/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Rinzii/ccmath) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9402/badge)](https://www.bestpractices.dev/projects/9402) -[![License](https://img.shields.io/badge/License-Apache%202.0%20WITH%20LLVM--exception-blue.svg)]( -https://opensource.org/licenses/Apache-2.0) +[![License](https://img.shields.io/badge/License-Apache%202.0%20WITH%20LLVM--exception-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Discord](https://img.shields.io/discord/1286067628456284244?label=Discord)](https://discord.gg/p3mVxAbdmc) -**CCMath** is a C++17 library designed to provide a reimplementation of the standard library, emphasizing -comprehensive constexpr support. +| Branch | Linux | macOS | Windows | +|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `main` | [![Linux main](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml?query=branch%3Amain) | [![macOS main](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml?query=branch%3Amain) | [![Windows main](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml/badge.svg?branch=main)](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml?query=branch%3Amain) | +| `release/0.3.x` | [![Linux release](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-linux.yml?query=branch%3Arelease%2F0.3.x) | [![macOS release](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-macos.yml?query=branch%3Arelease%2F0.3.x) | [![Windows release](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml/badge.svg?branch=release/0.3.x)](https://github.com/Rinzii/ccmath/actions/workflows/ci-windows.yml?query=branch%3Arelease%2F0.3.x) | + +CCMath is a header-only library with a ``-style API. The same entry points constexpr-evaluate where the standard +and the compiler allow, dispatch to portable runtime implementations everywhere else, and pick up SIMD on selected +functions when the target supports it. + +This README also describes what CCMath is working toward, so some of these claims are goals rather than finished +work. [STATUS.md](docs/STATUS.md) tracks where each module and function actually stands today. For the +approximation-driven function work, the implementation guide lives +in [APPROXIMATING_FUNCTIONS.pdf](docs/approximating_functions/APPROXIMATING_FUNCTIONS.pdf). Questions and design +discussion are welcome on [Discord](https://discord.gg/p3mVxAbdmc). + +--- + +## Contents + +- [Why CCMath](#why-ccmath) +- [Quick start](#quick-start) +- [Accuracy and conformance](#accuracy-and-conformance) +- [Performance](#performance) +- [Supported platforms and compilers](#supported-platforms-and-compilers) +- [Installation](#installation) +- [Validation](#validation) +- [Contributing](#contributing) +- [Projects using CCMath](#projects-using-ccmath) +- [License](#license) +- [Acknowledgments](#acknowledgments) + +--- -## Key Features +## Why CCMath -- **Full `constexpr` Compatibility**: Every function in CCMath is implemented as `constexpr`, ensuring compatibility - with `static_assert` and enabling robust compile-time evaluations. -- **Seamless `` Replacement**: CCMath replicates the functionality of the standard `` library, offering 1: - 1 API compatibility. CCMath covers the vast majority of mathematical operations, including trigonometric, exponential, - logarithmic, and more that are provided by the standard library. -- **Performance-Focused Design**: All implemented functions are optimized for both compile-time and runtime performance, - providing - comparable or faster execution than standard library implementations. -- **Cross-Platform Support**: Tested on major platforms (Windows, macOS, Linux) with GCC, Clang, MSVC, and more, CCMath - ensures compatibility across diverse environments. -- **No External Dependencies**: The library requires only a modern C++17-compliant compiler, with no additional - dependencies. -- **Header-Only Library**: Integration is straightforward—just include the headers in your project and start using - CCMath. +Most `` implementations are built around different tradeoffs, and few of them combine standards-oriented +semantics, constexpr evaluation, full runtime execution, SIMD, and portable in-tree code in one place. That combination +is what CCMath is after. -## Usage +| Library | Standards-Oriented | Constexpr | Runtime | SIMD | Portable Implementation | +|-------------------|--------------------|-----------|---------|------------------|-------------------------| +| `std::cmath` | Yes | Partial | Yes | Vendor dependent | No | +| Compiler builtins | Partial | Partial | Yes | No | No | +| GCEM | Partial | Yes | Limited | No | Yes | +| OpenLibm | Yes | No | Yes | Limited | Yes | +| SLEEF | Partial | No | Yes | Yes | Yes | +| CCMath | Yes | Yes | Yes | Yes | Yes | -Include CCMath in your project by adding the `` header file. Here's a simple example: +Numerical correctness and performance are not columns in the table, and each gets its own section below. Per-function +progress is tracked in [STATUS.md](docs/STATUS.md). + +--- + +## Quick start + +There is no separate constexpr API. When constant evaluation is possible the public symbol participates in it, and when +it is not, the same symbol dispatches to a runtime implementation. You write `ccm::sqrt` once and it works in both +contexts. + +The monolithic header pulls in everything: ```cpp -#include // Monolithic header that includes all functions like +#include #include int main() { - constexpr double result = ccm::sqrt(25.0); // Compile-time evaluation - std::cout << "Square root of 25: " << result << std::endl; - return 0; + constexpr double result = ccm::sqrt(25.0); + + std::cout << result << '\n'; } ``` -or if you prefer to include only specific functions: +You can also include a single function when that is all you need: ```cpp -#include // Function-specific header for free-standing functions +#include #include int main() { - constexpr double result = ccm::log(12.0); // Compile-time evaluation - std::cout << "Log of 12: " << result << std::endl; - return 0; + constexpr double result = ccm::log(12.0); + + std::cout << result << '\n'; } ``` -### Adding CCMath via CMake +Both the compile-time and the runtime forms go through the same entry point: + +```cpp +// Compile time: usable in a constant expression. +constexpr double a = ccm::sin(0.5); +static_assert(a > 0.0); + +// Runtime: the same call on a value the compiler cannot fold. +double exp_at(double x) { return ccm::exp(x); } +``` + +--- + +## Accuracy and conformance + +Accuracy is the first priority, ahead of performance. The goal for every function is a correctly rounded result under +all four IEEE-754 rounding modes. Coverage is function-by-function, so some already meet that bar today while others are +still getting there. + +| Status | What it means | +|------------------------------|------------------------------------------------------------------------------------| +| Correctly rounded, all modes | Bit-exact under all four IEEE-754 rounding modes | +| Correctly rounded, nearest | Bit-exact under round-to-nearest ties-to-even, other modes not yet covered | +| Bounded error | Within a documented bound of no more than 4 ULP until correct rounding is finished | + +Where each function sits on that scale is listed in [STATUS.md](docs/STATUS.md). A gap that is not written down there is +a bug. + +Alongside accuracy, CCMath is designed to follow the behavior the C and C++ standards pin down for ``. That +covers function semantics, domain and range handling, NaN propagation, infinity behavior, floating-point classification, +rounding behavior, and the edge cases the standards specify. Implemented functions follow those rules exactly. + +We cover the trigonometric, exponential, power, rounding, classification, and utility categories of the C and C++ +mathematical library, though several functions are not finished yet. + +--- + +## Performance -CCMath includes a comprehensive CMake setup for easy integration. Use `FetchContent` as shown below: +After accuracy, performance is the next thing we care about. The idea is that once a function is correct it should still +keep pace with the platform libm, so the common case stays quick and we only fall back to the fully accurate version +when we have to. A lot of functions also pick up SIMD where the hardware supports it, currently SSE2, SSE4, AVX2, and +ARM NEON. We keep benchmarks against the platform libm in the tree, so a regression is easy to catch. + +--- + +## Supported platforms and compilers + +CCMath builds and is tested across Linux, macOS, and Windows, under GCC, Clang, Apple Clang, and MSVC, on C++17 through +C++26 in both Debug and Release. The CI matrix that enforces this is described under [validation](#validation). + +| Compiler | Minimum version | +|----------------|-----------------| +| GCC | 11.1 | +| Clang | 9.0.0 | +| Apple Clang | 14.0.3 | +| MSVC | 19.26 | +| Intel DPC++ | 2022.0.0 | +| NVIDIA HPC SDK | 22.7 | + +> [!NOTE] +> Minimum compiler versions are not final yet, so treat them as guidance for now. + +--- + +## Installation + +The library itself has no required dependencies, so consuming it is just a matter of putting the headers where your +compiler can find them. + +### CMake + +CMake is the primary build system. ```cmake include(FetchContent) + FetchContent_Declare( - ccmath - GIT_REPOSITORY https://github.com/Rinzii/ccmath.git - GIT_TAG main # Or replace with a specific version (e.g., v0.2.0) + ccmath + GIT_REPOSITORY https://github.com/Rinzii/ccmath.git + GIT_TAG v0.3.0 ) + FetchContent_MakeAvailable(ccmath) -target_link_libraries(main PRIVATE ccmath::ccmath) +target_link_libraries( + main + PRIVATE + ccmath::ccmath +) ``` -Alternatively, you can directly include the headers in your project. +You can also vendor the headers directly. -## Supported Compilers +### Secondary build systems (Meson and Premake) -- GCC 11.1+ -- Clang 9.0.0+ -- AppleClang 14.0.3+ -- MSVC 19.26+ -- Intel DPC++ 2022.0.0+ -- Nvidia HPC SDK 22.7+ +CMake is the primary build system, but the repository also ships ready-to-use `meson.build` and `premake5.lua`. Both are +self-contained, since each generates `ccmath/version.hpp` itself, so a Meson or Premake consumer never needs a CMake +configure first. They expose include directories and compile definitions only, not examples, tests, benchmarks, or +fuzzing targets. -> [!ATTENTION] -> Listed compilers may not be the actual supported versions as development is still ongoing. +Meson, with ccmath as a subproject: -> [!NOTE] -> Currently working on finding manners to lower these requirements. +```meson +ccmath_dep = dependency('ccmath', fallback : ['ccmath', 'ccmath_dep']) +executable('my-app', 'main.cpp', dependencies : ccmath_dep) +``` -## Contributing +Premake: -CCMath is an open-source project, and contributions are always welcome! Whether you want to report bugs, suggest -features, or contribute code, check out our [contribution guidelines](CONTRIBUTING.md). +```lua +include("path/to/ccmath/premake5.lua") -## Join Our Discord! +project "my-app" + kind "ConsoleApp" + language "C++" + files { "main.cpp" } + ccmath.use() +``` + +--- -Have questions, ideas, or just want to chat? Join our Discord server—we’d love to hear from you! +## Validation -[![Discord Banner](https://discord.com/api/guilds/1286067628456284244/widget.png?style=banner2)](https://discord.gg/p3mVxAbdmc) +CCMath is validated thoroughly. The approach borrows from LLVM-libc, CORE-MATH, and glibc: every public entry point is +checked for standards edge cases and ULP error against the platform libm. Beyond that, a rigorous suite cross-checks the +harder cases against MPFR and CORE-MATH oracles under all four IEEE rounding modes, Gappa and Sollya back the proof +artifacts, and differential fuzzing catches kernel disagreements. All of it runs in CI across Linux, macOS, and Windows, +under GCC, Clang, Apple Clang, and MSVC, on C++17 through C++26, alongside CodeQL, OpenSSF Scorecard, and +warning-as-error builds. -## Implementation Status +How to run any of these suites locally is documented in [CONTRIBUTING.md](CONTRIBUTING.md), and per-function accuracy +status is tracked in [STATUS.md](docs/STATUS.md). -For details on the current progress of the library, including completed features and planned additions, refer to -the [implementation status](docs/STATUS.md) document. +--- -## Projects Using CCMath +## Contributing -- [Fornani](https://github.com/swagween/fornani) - A 2D action-adventure game in development. +See [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), and [SECURITY.md](SECURITY.md). Bug +reports and design questions go to GitHub Issues or Discord. -_(Are you using CCMath? Let us know!)_ +--- + +## Projects using CCMath + +The following projects publicly use CCMath: + +- [Fornani](https://github.com/swagween/fornani) + +If your project uses CCMath and is publicly available, open a PR to add it to this list. + +--- ## License -CCMath is licensed under the Apache-2.0 WITH LLVM-exception. See the LICENSE file for more details. +Apache License v2.0 with LLVM Exceptions. See [LICENSE](LICENSE). + +--- -## Special Thanks +## Acknowledgments -A special thanks to LLVM, GCC, and the many public LibM's for providing inspiration and insights during the development -of CCMath. +Algorithm and validation ideas draw on LLVM-libc, GCC libm, CORE-MATH, glibc math, and OpenLibm. diff --git a/SECURITY.md b/SECURITY.md index 5641c5e5..6b46c6e7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,39 +1,30 @@ -# Security Policy for CCMath - -## 1. Introduction -This security policy outlines the procedures and guidelines for reporting vulnerabilities and managing security issues related to the CCMath library. Our commitment is to provide only the essential support necessary to maintain the security of this project. - -## 2. Supported Versions -We only support the latest released version of the CCMath library, according to [Semantic Versioning (SemVer)](https://semver.org/). If you are using an older version, we strongly encourage you to upgrade to the latest version to ensure you receive any security updates. - -## 3. Reporting a Vulnerability -If you discover a security vulnerability in ccmath, please disclose it responsibly by following these steps: -1. **Contact**: Email the details to ianpike98(at)gmail(dot)com. Please include: - - A detailed description of the vulnerability. - - Steps to reproduce the issue. - - Any potential impact or threat associated with the vulnerability. -2. **Do Not**: Publicly disclose the vulnerability until we have had a chance to address it. -3. **Acknowledgment**: We will acknowledge your report within 7 days and provide an estimated timeline for addressing the vulnerability. - -## 4. Response Process -Upon receiving a vulnerability report, we will: -1. **Investigate**: Confirm and reproduce the issue. -2. **Assess**: Evaluate the severity and potential impact. -3. **Mitigate**: Develop and test a fix, if deemed necessary. -4. **Patch**: Release a patch only for the latest version. -5. **Notify**: Inform the reporter once the vulnerability is resolved. - -## 5. Public Disclosure -Once the vulnerability is resolved, we will: -- Issue a brief advisory outlining the vulnerability, its impact, and the resolution. -- Credit the individual who reported the vulnerability unless they wish to remain anonymous. - -## 6. Security Best Practices -We recommend that users of the CCMath library: -- Keep their CCMath version up to date by always using the latest release. -- Regularly review and apply security patches as they become available. -- Follow secure coding practices when integrating CCMath into their projects. - -## 7. Security Contact Information -If you have any questions or need further assistance, please contact us at ianpike98(at)gmail(dot)com. +# Security policy +## Supported versions + +Only the latest release tagged under [Semantic Versioning](https://semver.org/) gets +security fixes. Older tags are unsupported. + +## Reporting + +Email ianpike98(at)gmail(dot)com with: + +- What you found +- Steps to reproduce +- Impact you expect + +Do not open a public issue for an unfixed vulnerability. + +We aim to acknowledge within 7 days and send a rough fix timeline when the report +checks out. + +## Our response + +1. Reproduce and confirm +2. Decide severity +3. Patch on `dev`, ship in the next release if needed +4. Credit the reporter in the advisory unless they want anonymity + +## Contact + +Security questions: ianpike98(at)gmail(dot)com diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 11984720..cd67ee95 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,42 +1,23 @@ +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) - -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - -project(ccmath-benchmark) +project(ccmath-benchmark LANGUAGES CXX) option(CCM_BENCH_BASIC "Enable basic benchmarks" OFF) option(CCM_BENCH_COMPARE "Enable comparison benchmarks" OFF) option(CCM_BENCH_POWER "Enable power benchmarks" OFF) option(CCM_BENCH_NEAREST "Enable nearest benchmarks" ON) - option(CCM_BENCH_ALL "Enable all benchmarks" OFF) -# Force cmake to use Release if debug is detected if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(CMAKE_BUILD_TYPE "Release" ON CACHE STRING "Build type" FORCE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE) endif () if (NOT TARGET ccmath) find_package(ccmath CONFIG REQUIRED) endif () -function(add_benchmark function_name source_files) - add_executable(ccm_benchmark_${function_name} helpers/randomizers.hpp ${source_files}) - target_link_libraries(ccm_benchmark_${function_name} PRIVATE ccmath::ccmath benchmark::benchmark) - target_compile_features(ccm_benchmark_${function_name} PRIVATE cxx_std_17) -endfunction() +set(CCMATH_BENCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/config/BenchmarkModuleRegistry.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/helpers/CcmBenchHelpers.cmake) - -if (CCM_BENCH_BASIC) - add_benchmark(abs benchmarks/basic/abs.bench.cpp benchmarks/basic/abs.bench.hpp) - add_benchmark(fdim benchmarks/basic/fdim.bench.cpp benchmarks/basic/fdim.bench.hpp) - add_benchmark(fma benchmarks/basic/fma.bench.cpp benchmarks/basic/fma.bench.hpp) -endif () - -if (CCM_BENCH_POWER) - add_benchmark(sqrt benchmarks/power/sqrt.bench.cpp benchmarks/power/sqrt.bench.hpp) -endif () - -if (CCM_BENCH_NEAREST) - #add_benchmark(trunc benchmarks/nearest/trunc.bench.cpp benchmarks/nearest/trunc.bench.hpp) -endif () +ccmath_apply_bench_registry() diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..1f9f74b3 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,38 @@ +# Benchmark tooling + +BenchmarkModuleRegistry.cmake maps modules to function lists. CcmBenchHelpers.cmake builds ccm_benchmark_ executables via ccmath_apply_bench_registry. + +## Options + +| Option | Default | +| --- | --- | +| CCM_BENCH_BASIC | OFF | +| CCM_BENCH_POWER | OFF | +| CCM_BENCH_NEAREST | ON | +| CCM_BENCH_ALL | OFF | + +Aggregate target: ccm_benchmark_all. + +## Macros (benchmarks/shared/register.hpp) + +| Macro | Registers | +| --- | --- | +| CCMATH_BENCH_UNARY_COMPARE | std vs ccm on scalar and random vectors, CT path | +| CCMATH_BENCH_*_COMPARE_PATHS | Same with _rt suites when the ccm body forces runtime | +| CCMATH_BENCH_*_PROFILE | Fixed literal, named for asmlab profiles | +| CCMATH_BENCH_*_PROFILE_PATHS | Profile with CT and RT | + +Per-target path opt-out: CCM_BM_CONFIG_NO_CT or CCM_BM_CONFIG_NO_RT through ccmath_add_bench_target COMPILE_DEFINITIONS. + +## New function + +Add benchmarks/src/math//foo.bench.cpp, append foo to CCMATH_BENCH_MODULE__FUNCTIONS, enable the module option at configure. + +## Run + +```bash +cmake --preset -DCCM_BENCH_POWER=ON +cmake --build out/ --target ccm_benchmark_sqrt +``` + +powf_impl and other impl-kernel benches are asmlab targets under tools/asmlab/bench/, added when CCM_BENCH_POWER is on. diff --git a/benchmarks/benchmarks/basic/abs.bench.cpp b/benchmarks/benchmarks/basic/abs.bench.cpp deleted file mode 100644 index 3781303d..00000000 --- a/benchmarks/benchmarks/basic/abs.bench.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2024-Present Ian Pike - * Copyright (c) 2024-Present ccmath contributors - * - * This library is provided under the MIT License. - * See LICENSE for more information. - */ - -#include "abs.bench.hpp" - -// NOLINTBEGIN - - -BENCHMARK(BM_basic_abs_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_abs_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_abs_rand_int_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_abs_rand_int_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_abs_rand_double_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_abs_rand_double_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK_MAIN(); - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/basic/abs.bench.hpp b/benchmarks/benchmarks/basic/abs.bench.hpp deleted file mode 100644 index 040d63cd..00000000 --- a/benchmarks/benchmarks/basic/abs.bench.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -* Copyright (c) 2024-Present Ian Pike -* Copyright (c) 2024-Present ccmath contributors -* -* This library is provided under the MIT License. -* See LICENSE for more information. -*/ - -#include "../../helpers/randomizers.hpp" -#include -#include -#include - -namespace bm = benchmark; - -// NOLINTBEGIN - - -static void BM_basic_abs_std(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(std::abs(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_abs_ccmath(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(ccm::abs(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_abs_rand_int_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(std::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_abs_rand_int_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(ccm::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_abs_rand_double_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - benchmark::DoNotOptimize(std::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_abs_rand_double_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - benchmark::DoNotOptimize(ccm::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} - - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/basic/fdim.bench.cpp b/benchmarks/benchmarks/basic/fdim.bench.cpp deleted file mode 100644 index b25b95ff..00000000 --- a/benchmarks/benchmarks/basic/fdim.bench.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) 2024-Present Ian Pike -* Copyright (c) 2024-Present ccmath contributors -* -* This library is provided under the MIT License. -* See LICENSE for more information. -*/ - -#include "fdim.bench.hpp" - -// NOLINTBEGIN - -BENCHMARK(BM_basic_fdim_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fdim_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fdim_rand_int_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fdim_rand_int_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fdim_rand_double_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fdim_rand_double_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK_MAIN(); - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/basic/fdim.bench.hpp b/benchmarks/benchmarks/basic/fdim.bench.hpp deleted file mode 100644 index ffcb3634..00000000 --- a/benchmarks/benchmarks/basic/fdim.bench.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/* -* Copyright (c) 2024-Present Ian Pike -* Copyright (c) 2024-Present ccmath contributors -* -* This library is provided under the MIT License. -* See LICENSE for more information. - */ - -#include "../../helpers/randomizers.hpp" -#include -#include -#include - -namespace bm = benchmark; - -// NOLINTBEGIN - -static void BM_basic_fdim_std(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(std::fdim(state.range(0), state.range(1))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fdim_ccmath(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(ccm::fdim(state.range(0), state.range(1))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fdim_rand_int_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers2 = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomIntegers.size(); ++i) { - benchmark::DoNotOptimize(std::fdim(randomIntegers[i], randomIntegers2[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fdim_rand_int_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers2 = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomIntegers.size(); ++i) { - benchmark::DoNotOptimize(ccm::fdim(randomIntegers[i], randomIntegers2[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fdim_rand_double_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles2 = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomDoubles.size(); ++i) { - benchmark::DoNotOptimize(std::fdim(randomDoubles[i], randomDoubles2[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fdim_rand_double_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles2 = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomDoubles.size(); ++i) { - benchmark::DoNotOptimize(ccm::fdim(randomDoubles[i], randomDoubles2[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/basic/fma.bench.cpp b/benchmarks/benchmarks/basic/fma.bench.cpp deleted file mode 100644 index 30711ede..00000000 --- a/benchmarks/benchmarks/basic/fma.bench.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) 2024-Present Ian Pike -* Copyright (c) 2024-Present ccmath contributors -* -* This library is provided under the MIT License. -* See LICENSE for more information. -*/ - -#include "fma.bench.hpp" - -// NOLINTBEGIN - -BENCHMARK(BM_basic_fma_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fma_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fma_rand_int_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fma_rand_int_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fma_rand_double_std)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK(BM_basic_fma_rand_double_ccmath)->RangeMultiplier(2)->Range(8, 8<<10)->Complexity(); - -BENCHMARK_MAIN(); - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/basic/fma.bench.hpp b/benchmarks/benchmarks/basic/fma.bench.hpp deleted file mode 100644 index 4a105efb..00000000 --- a/benchmarks/benchmarks/basic/fma.bench.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -* Copyright (c) 2024-Present Ian Pike -* Copyright (c) 2024-Present ccmath contributors -* -* This library is provided under the MIT License. -* See LICENSE for more information. - */ - -#include "../../helpers/randomizers.hpp" -#include -#include -#include - -namespace bm = benchmark; - -// NOLINTBEGIN - -static void BM_basic_fma_std(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(std::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fma_ccmath(benchmark::State& state) { - for ([[maybe_unused]] auto _ : state) { - benchmark::DoNotOptimize(ccm::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fma_rand_int_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers2 = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers3 = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomIntegers.size(); ++i) { - benchmark::DoNotOptimize(std::fma(randomIntegers[i], randomIntegers2[i], randomIntegers3[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fma_rand_int_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers2 = ran.generateRandomIntegers(state.range(0)); - auto randomIntegers3 = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomIntegers.size(); ++i) { - benchmark::DoNotOptimize(ccm::fma(randomIntegers[i], randomIntegers2[i], randomIntegers3[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fma_rand_double_std(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles2 = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles3 = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomDoubles.size(); ++i) { - benchmark::DoNotOptimize(std::fma(randomDoubles[i], randomDoubles2[i], randomDoubles3[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_basic_fma_rand_double_ccmath(benchmark::State& state) { - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles2 = ran.generateRandomDoubles(state.range(0)); - auto randomDoubles3 = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) { - for (size_t i = 0; i < randomDoubles.size(); ++i) { - benchmark::DoNotOptimize(ccm::fma(randomDoubles[i], randomDoubles2[i], randomDoubles3[i])); - } - } - state.SetComplexityN(state.range(0)); -} - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/nearest/trunc.bench.hpp b/benchmarks/benchmarks/nearest/trunc.bench.hpp deleted file mode 100644 index e69de29b..00000000 diff --git a/benchmarks/benchmarks/power/sqrt.bench.cpp b/benchmarks/benchmarks/power/sqrt.bench.cpp deleted file mode 100644 index 0e8c0f88..00000000 --- a/benchmarks/benchmarks/power/sqrt.bench.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2024-Present Ian Pike - * Copyright (c) 2024-Present ccmath contributors - * - * This library is provided under the MIT License. - * See LICENSE for more information. - */ - -#include "sqrt.bench.hpp" - -// NOLINTBEGIN - -// Regular -#ifndef CCM_BM_CONFIG_NO_CT - -BENCHMARK(BM_power_sqrt_std)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_ccm)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_int_std)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_int_ccm)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_double_std)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_double_ccm)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); -#endif - -#ifndef CCM_BM_CONFIG_NO_CT - // Runtime - #if !(defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__)) // GCC 6.1+ has constexpr sqrt builtins. - -BENCHMARK(BM_power_sqrt_std_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_ccm_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_int_std_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_int_ccm_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_double_std_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - -BENCHMARK(BM_power_sqrt_rand_double_ccm_rt)->RangeMultiplier(2)->Range(8, 8 << 10)->Complexity(); - - #endif -#endif - -BENCHMARK_MAIN(); - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/benchmarks/power/sqrt.bench.hpp b/benchmarks/benchmarks/power/sqrt.bench.hpp deleted file mode 100644 index 8285da6e..00000000 --- a/benchmarks/benchmarks/power/sqrt.bench.hpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2024-Present Ian Pike - * Copyright (c) 2024-Present ccmath contributors - * - * This library is provided under the MIT License. - * See LICENSE for more information. - */ - -#include "../../helpers/randomizers.hpp" - -#include -#include -#include - -namespace bm = benchmark; - -// NOLINTBEGIN -// std::sqrt - - -#ifndef CCM_BM_CONFIG_NO_CT -static void BM_power_sqrt_std(benchmark::State & state) -{ - for ([[maybe_unused]] auto _ : state) { benchmark::DoNotOptimize(std::sqrt(static_cast(state.range(0)))); } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_ccm(benchmark::State & state) -{ - for ([[maybe_unused]] auto _ : state) { benchmark::DoNotOptimize(ccm::sqrt(static_cast(state.range(0)))); } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_int_std(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomIntegers) { benchmark::DoNotOptimize(std::sqrt(static_cast(x))); } - } - state.SetComplexityN(state.range(0)); -} -// -static void BM_power_sqrt_rand_int_ccm(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomIntegers) { benchmark::DoNotOptimize(ccm::sqrt(static_cast(x))); } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_double_std(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomDoubles) { benchmark::DoNotOptimize(std::sqrt(x)); } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_double_ccm(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomDoubles) { benchmark::DoNotOptimize(ccm::sqrt(x)); } - } - state.SetComplexityN(state.range(0)); -} - -#endif - -// Runtime evaluation - -#ifndef CCM_BM_CONFIG_NO_RT - #if !(defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__)) // GCC 6.1+ has constexpr sqrt builtins. - -inline double sqrt_rt_bm(double v) -{ - return ccm::sqrt(v); -} - -static void BM_power_sqrt_std_rt(benchmark::State & state) -{ - for ([[maybe_unused]] auto _ : state) { benchmark::DoNotOptimize(std::sqrt(static_cast(state.range(0)))); } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_ccm_rt(benchmark::State & state) -{ - for ([[maybe_unused]] auto _ : state) { benchmark::DoNotOptimize(sqrt_rt_bm(static_cast(state.range(0)))); } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_int_std_rt(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomIntegers) { benchmark::DoNotOptimize(std::sqrt(static_cast(x))); } - } - state.SetComplexityN(state.range(0)); -} -// -static void BM_power_sqrt_rand_int_ccm_rt(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomIntegers = ran.generateRandomIntegers(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomIntegers) { benchmark::DoNotOptimize(sqrt_rt_bm(static_cast(x))); } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_double_std_rt(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomDoubles) { benchmark::DoNotOptimize(std::sqrt(x)); } - } - state.SetComplexityN(state.range(0)); -} - -static void BM_power_sqrt_rand_double_ccm_rt(benchmark::State & state) -{ - ccm::bench::Randomizer ran; - auto randomDoubles = ran.generateRandomDoubles(state.range(0)); - while (state.KeepRunning()) - { - for (auto x : randomDoubles) { benchmark::DoNotOptimize(sqrt_rt_bm(x)); } - } - state.SetComplexityN(state.range(0)); -} - - #endif -#endif - -// NOLINTEND \ No newline at end of file diff --git a/benchmarks/ccmath_benchmark_main.cpp b/benchmarks/ccmath_benchmark_main.cpp deleted file mode 100644 index 57137ce8..00000000 --- a/benchmarks/ccmath_benchmark_main.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2024-Present Ian Pike - * Copyright (c) 2024-Present ccmath contributors - * - * This library is provided under the MIT License. - * See LICENSE for more information. - */ - -#include -#include -#include - -#include -#include "ccmath/ccmath.hpp" -#include "helpers/randomizers.hpp" - -// NOLINTBEGIN - -namespace bm = benchmark; - -static ccm::bench::Randomizer ran{}; - -static void BM_basic_abs_ccm(benchmark::State& state) { - auto randomIntegers = ran.generateRandomIntegers(static_cast(state.range(0))); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(std::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_basic_abs_ccm)->Range(8, 8<<10)->Complexity(); - -static void BM_basic_abs_std(benchmark::State& state) { - auto randomIntegers = ran.generateRandomIntegers(static_cast(state.range(0))); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(ccm::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_basic_abs_std)->Range(8, 8<<10)->Complexity(); - - - - -/* - -static void BM_std_fma(bm::State& state) { - for ([[maybe_unused]] auto _ : state) { - bm::DoNotOptimize(std::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -static void BM_ccm_fma(bm::State& state) { - for ([[maybe_unused]] auto _ : state) { - bm::DoNotOptimize(ccm::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -*/ - -/* -// Benchmarking std::abs with the same set of random integers -static void BM_std_abs_rand_int(benchmark::State& state) { - auto randomIntegers = generateRandomIntegers(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(std::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_abs_rand_int)->Range(8, 8<<10)->Complexity(); - -// Benchmarking ccm::abs with the same set of random integers -static void BM_ccm_abs_rand_int(benchmark::State& state) { - auto randomIntegers = generateRandomIntegers(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(ccm::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_abs_rand_int)->Range(8, 8<<10)->Complexity(); -*/ -/* - -// Benchmarking std::abs with the same set of random integers -static void BM_std_abs_rand_double(benchmark::State& state) { - auto randomIntegers = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(std::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_abs_rand_double)->Range(8, 8<<10)->Complexity(); - -// Benchmarking ccm::abs with the same set of random integers -static void BM_ccm_abs_rand_double(benchmark::State& state) { - auto randomIntegers = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomIntegers) { - benchmark::DoNotOptimize(ccm::abs(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_abs_rand_double)->Range(8, 8<<10)->Complexity(); - - -static void BM_std_abs(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(std::abs(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_abs)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - -static void BM_ccm_abs(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(ccm::abs(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_abs)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - - -static void BM_std_log_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(std::log(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_log_rand_double)->Range(8, 8<<10)->Complexity(); - -static void BM_ccm_log_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(ccm::log(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_log_rand_double)->Range(8, 8<<10)->Complexity(); - -static void BM_ccm_log(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(ccm::log(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_log)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - -static void BM_std_log(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(std::log(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_log)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - -static void BM_std_log2_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(std::log2(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_log2_rand_double)->Range(8, 8<<10)->Complexity(); - -static void BM_ccm_log2_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(ccm::log2(x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_log2_rand_double)->Range(8, 8<<10)->Complexity(); - -static void BM_ccm_log2(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(ccm::log2(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_log2)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - -static void BM_std_log2(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(std::log2(state.range(0))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_log2)->Arg(16)->Arg(256)->Arg(4096)->Arg(65536)->Complexity(); - -static void BM_ccm_lerp(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(ccm::lerp(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_lerp)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -static void BM_ccm_lerp2(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(ccm::lerp2(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_lerp2)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - - -static void BM_std_lerp(bm::State& state) { - for (auto _ : state) { - bm::DoNotOptimize(ccm::lerp(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_lerp)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -*/ - -// fma -static void BM_std_fma(bm::State& state) { - for ([[maybe_unused]] auto _ : state) { - bm::DoNotOptimize(std::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -static void BM_ccm_fma(bm::State& state) { - for ([[maybe_unused]] auto _ : state) { - bm::DoNotOptimize(ccm::fma(state.range(0), state.range(1), state.range(2))); - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_fma)->Args({16, 16, 16})->Args({256, 256, 256})->Args({4096, 4096, 4096})->Args({65536, 65536, 65536})->Complexity(); - -static void BM_std_fma_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(std::fma(x, x, x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_std_fma_rand_double)->Range(8, 8<<10)->Complexity(); - -static void BM_ccm_fma_rand_double(bm::State& state) { - auto randomDoubles = generateRandomDoubles(static_cast(state.range(0)), DefaultSeed); - while (state.KeepRunning()) { - for (auto x : randomDoubles) { - bm::DoNotOptimize(ccm::fma(x, x, x)); - } - } - state.SetComplexityN(state.range(0)); -} -BENCHMARK(BM_ccm_fma_rand_double)->Range(8, 8<<10)->Complexity(); - - - -BENCHMARK_MAIN(); - - -// NOLINTEND diff --git a/benchmarks/helpers/randomizers.hpp b/benchmarks/helpers/randomizers.hpp deleted file mode 100644 index d7a700de..00000000 --- a/benchmarks/helpers/randomizers.hpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2024-Present Ian Pike - * Copyright (c) 2024-Present ccmath contributors - * - * This library is provided under the MIT License. - * See LICENSE for more information. - */ - -#pragma once - -#include -#include -#include - -namespace ccm::bench -{ - struct Randomizer - { - public: - explicit Randomizer(std::uint_fast32_t seed = 937162211) : m_gen{seed} {} - - std::vector generateRandomIntegers(std::int64_t count, int min = std::numeric_limits::min(), int max = std::numeric_limits::max()) - { - assert(count > 0); - std::vector randomIntegers; - randomIntegers.reserve(static_cast(count)); - std::uniform_int_distribution dist(min, max); - for (std::int64_t i = 0; i < count; ++i) { randomIntegers.push_back(dist(m_gen)); } - return randomIntegers; - } - - std::vector generateRandomDoubles(std::int64_t count, double min = 0.0, - double max = 1.0) - { - assert(count > 0); - std::vector randomDouble; - randomDouble.reserve(static_cast(count)); - std::uniform_real_distribution dist(min, max); - for (std::int64_t i = 0; i < count; ++i) { randomDouble.push_back(dist(m_gen)); } - return randomDouble; - } - - private: - std::mt19937 m_gen; - }; -} // namespace ccm::bench \ No newline at end of file diff --git a/benchmarks/shared/harness.hpp b/benchmarks/shared/harness.hpp new file mode 100644 index 00000000..5917e56c --- /dev/null +++ b/benchmarks/shared/harness.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "inputs.hpp" + +#include + +#include +#include +#include +#include + +namespace ccm::bench +{ + inline constexpr std::int64_t kRangeMin = 8; + inline constexpr std::int64_t kRangeMax = 8 << 10; + + template T load_arg(T value) + { + volatile T storage = value; + return storage; + } + + template void unary_fixed(benchmark::State & state, T value, Fn && fn) + { + T const x = load_arg(value); + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x)); + } + } + + template void binary_fixed(benchmark::State & state, T x_value, T y_value, Fn && fn) + { + T const x = load_arg(x_value); + T const y = load_arg(y_value); + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y)); + } + } + + template void ternary_fixed(benchmark::State & state, T x_value, T y_value, T z_value, Fn && fn) + { + T const x = load_arg(x_value); + T const y = load_arg(y_value); + T const z = load_arg(z_value); + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y, z)); + } + } + + template void unary_scalar(benchmark::State & state, Fn && fn) + { + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(static_cast(state.range(0)))); + } + state.SetComplexityN(state.range(0)); + } + + template void unary_vector(benchmark::State & state, Fn && fn, Vec const & inputs) + { + for (auto _ : state) + { + for (auto const & x : inputs) + { + benchmark::DoNotOptimize(fn(x)); + } + } + state.SetComplexityN(state.range(0)); + } + + template void binary_scalar(benchmark::State & state, Fn && fn) + { + T const x = static_cast(state.range(0)); + T const y = static_cast(state.range(0)); + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y)); + } + state.SetComplexityN(state.range(0)); + } + + template void ternary_scalar(benchmark::State & state, Fn && fn) + { + T const x = static_cast(state.range(0)); + T const y = static_cast(state.range(0)); + T const z = static_cast(state.range(0)); + for (auto _ : state) + { + benchmark::DoNotOptimize(fn(x, y, z)); + } + state.SetComplexityN(state.range(0)); + } + + template + void ternary_vector(benchmark::State & state, Fn && fn, VecX const & xs, VecY const & ys, VecZ const & zs) + { + for (auto _ : state) + { + for (std::size_t i = 0; i < xs.size(); ++i) + { + benchmark::DoNotOptimize(fn(xs[i], ys[i], zs[i])); + } + } + state.SetComplexityN(state.range(0)); + } + + template void binary_vector(benchmark::State & state, Fn && fn, VecX const & xs, VecY const & ys) + { + for (auto _ : state) + { + for (std::size_t i = 0; i < xs.size(); ++i) + { + benchmark::DoNotOptimize(fn(xs[i], ys[i])); + } + } + state.SetComplexityN(state.range(0)); + } + +} // namespace ccm::bench diff --git a/benchmarks/shared/inputs.hpp b/benchmarks/shared/inputs.hpp new file mode 100644 index 00000000..ff0fda63 --- /dev/null +++ b/benchmarks/shared/inputs.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ccm::bench +{ + struct Randomizer + { + explicit Randomizer(std::uint_fast32_t seed = 937162211) : m_gen{ seed } {} + + std::vector integers(std::int64_t count, int min = std::numeric_limits::min(), int max = std::numeric_limits::max()) + { + assert(count > 0); + std::vector out; + out.reserve(static_cast(count)); + std::uniform_int_distribution dist(min, max); + for (std::int64_t i = 0; i < count; ++i) + { + out.push_back(dist(m_gen)); + } + return out; + } + + std::vector doubles(std::int64_t count, double min = 0.0, double max = 1.0) + { + assert(count > 0); + std::vector out; + out.reserve(static_cast(count)); + std::uniform_real_distribution dist(min, max); + for (std::int64_t i = 0; i < count; ++i) + { + out.push_back(dist(m_gen)); + } + return out; + } + + private: + std::mt19937 m_gen; + }; + + inline Randomizer & thread_rng() + { + static thread_local Randomizer rng; + return rng; + } + + inline std::vector random_integers(std::int64_t count) + { + return thread_rng().integers(count); + } + + inline std::vector random_doubles(std::int64_t count) + { + return thread_rng().doubles(count); + } + +} // namespace ccm::bench diff --git a/benchmarks/shared/paths.hpp b/benchmarks/shared/paths.hpp new file mode 100644 index 00000000..4fab3397 --- /dev/null +++ b/benchmarks/shared/paths.hpp @@ -0,0 +1,35 @@ +#pragma once + +// Compile-time path guards for benchmark suites. +// Per-target: -DCCM_BM_CONFIG_NO_CT or -DCCM_BM_CONFIG_NO_RT via CMake. + +#if !defined(CCM_BM_CONFIG_NO_CT) + #define CCMATH_BENCH_HAS_CT 1 +#else + #define CCMATH_BENCH_HAS_CT 0 +#endif + +#if !defined(CCM_BM_CONFIG_NO_RT) + #define CCMATH_BENCH_HAS_RT 1 +#else + #define CCMATH_BENCH_HAS_RT 0 +#endif + +// GCC libstdc++ constexpr std::sqrt makes RT-vs-std comparison meaningless on GCC 6.1+. +#if CCMATH_BENCH_HAS_RT && (defined(__GNUC__) && (__GNUC__ > 6 || (__GNUC__ == 6 && __GNUC_MINOR__ >= 1)) && !defined(__clang__)) + #define CCMATH_BENCH_HAS_RT_SUITE 0 +#else + #define CCMATH_BENCH_HAS_RT_SUITE CCMATH_BENCH_HAS_RT +#endif + +#if CCMATH_BENCH_HAS_CT + #define CCMATH_BENCH_IF_CT(...) __VA_ARGS__ +#else + #define CCMATH_BENCH_IF_CT(...) +#endif + +#if CCMATH_BENCH_HAS_RT_SUITE + #define CCMATH_BENCH_IF_RT(...) __VA_ARGS__ +#else + #define CCMATH_BENCH_IF_RT(...) +#endif diff --git a/benchmarks/shared/register.hpp b/benchmarks/shared/register.hpp new file mode 100644 index 00000000..6a933aac --- /dev/null +++ b/benchmarks/shared/register.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include "harness.hpp" +#include "paths.hpp" + +#include + +#include + +#define CCMATH_BENCH_APPLY_RANGE ->RangeMultiplier(2)->Range(ccm::bench::kRangeMin, ccm::bench::kRangeMax)->Complexity() + +#define CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::unary_scalar(state, [&](std::int64_t x) { StdBody; }); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::unary_scalar(state, [&](std::int64_t x) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto inputs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::unary_vector(state, [&](int x) { StdBody; }, inputs); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto inputs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::unary_vector(state, [&](int x) { CcmBody; }, inputs); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto inputs = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::unary_vector(state, [&](double x) { StdBody; }, inputs); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto inputs = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::unary_vector(state, [&](double x) { CcmBody; }, inputs); \ + } + +#define CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, Suffix) \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; + +#define CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::binary_scalar(state, [&](double x, double y) { StdBody; }); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::binary_scalar(state, [&](double x, double y) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::binary_vector(state, [&](int x, int y) { StdBody; }, xs, ys); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::binary_vector(state, [&](int x, int y) { CcmBody; }, xs, ys); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto ys = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::binary_vector(state, [&](double x, double y) { StdBody; }, xs, ys); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto ys = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::binary_vector(state, [&](double x, double y) { CcmBody; }, xs, ys); \ + } + +#define CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, Suffix) \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; + +#define CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, Suffix, StdBody, CcmBody) \ + static void BM_##Module##_##Name##_std##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { StdBody; }); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_scalar(benchmark::State & state) \ + { \ + ccm::bench::ternary_scalar(state, [&](double x, double y, double z) { CcmBody; }); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + auto zs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::ternary_vector(state, [&](int x, int y, int z) { StdBody; }, xs, ys, zs); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_int(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_integers(state.range(0)); \ + auto ys = ccm::bench::random_integers(state.range(0)); \ + auto zs = ccm::bench::random_integers(state.range(0)); \ + ccm::bench::ternary_vector(state, [&](int x, int y, int z) { CcmBody; }, xs, ys, zs); \ + } \ + static void BM_##Module##_##Name##_std##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto ys = ccm::bench::random_doubles(state.range(0)); \ + auto zs = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::ternary_vector(state, [&](double x, double y, double z) { StdBody; }, xs, ys, zs); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_rand_double(benchmark::State & state) \ + { \ + auto xs = ccm::bench::random_doubles(state.range(0)); \ + auto ys = ccm::bench::random_doubles(state.range(0)); \ + auto zs = ccm::bench::random_doubles(state.range(0)); \ + ccm::bench::ternary_vector(state, [&](double x, double y, double z) { CcmBody; }, xs, ys, zs); \ + } + +#define CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, Suffix) \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_scalar) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_int) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_rand_double) CCMATH_BENCH_APPLY_RANGE; + +#define CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, Suffix, Profile, Value, StdBody, CcmBody) \ + static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State & state) \ + { \ + ccm::bench::unary_fixed(state, Value, [&](double x) { StdBody; }); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State & state) \ + { \ + ccm::bench::unary_fixed(state, Value, [&](double x) { CcmBody; }); \ + } + +#define CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, Suffix, Profile) \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_##Profile); \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_##Profile); + +#define CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, Suffix, Profile, X, Y, StdBody, CcmBody) \ + static void BM_##Module##_##Name##_std##Suffix##_##Profile(benchmark::State & state) \ + { \ + ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { StdBody; }); \ + } \ + static void BM_##Module##_##Name##_ccm##Suffix##_##Profile(benchmark::State & state) \ + { \ + ccm::bench::binary_fixed(state, X, Y, [&](double x, double y) { CcmBody; }); \ + } + +#define CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, Suffix, Profile) \ + BENCHMARK(BM_##Module##_##Name##_std##Suffix##_##Profile); \ + BENCHMARK(BM_##Module##_##Name##_ccm##Suffix##_##Profile); + +#define CCMATH_BENCH_UNARY_COMPARE(Module, Name, StdBody, CcmBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, )) + +#define CCMATH_BENCH_UNARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, )) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, _rt)) + +#define CCMATH_BENCH_BINARY_COMPARE(Module, Name, StdBody, CcmBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, )) + +#define CCMATH_BENCH_BINARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, )) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, _rt)) + +#define CCMATH_BENCH_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, )) + +#define CCMATH_BENCH_TERNARY_COMPARE_PATHS(Module, Name, StdBody, CcmCtBody, CcmRtBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmCtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, )) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, _rt, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, _rt)) + +#define CCMATH_BENCH_UNARY_PROFILE(Module, Name, Profile, Value, StdBody, CcmBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, , Profile, Value, StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, , Profile)) + +#define CCMATH_BENCH_UNARY_PROFILE_PATHS(Module, Name, Profile, Value, StdBody, CcmCtBody, CcmRtBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, , Profile, Value, StdBody, CcmCtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, , Profile)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_UNARY_PROFILE_SUITE(Module, Name, _rt, Profile, Value, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_UNARY_PROFILE_SUITE(Module, Name, _rt, Profile)) + +#define CCMATH_BENCH_BINARY_PROFILE(Module, Name, Profile, X, Y, StdBody, CcmBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, , Profile, X, Y, StdBody, CcmBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, , Profile)) + +#define CCMATH_BENCH_BINARY_PROFILE_PATHS(Module, Name, Profile, X, Y, StdBody, CcmCtBody, CcmRtBody) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, , Profile, X, Y, StdBody, CcmCtBody)) \ + CCMATH_BENCH_IF_CT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, , Profile)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_DECLARE_BINARY_PROFILE_SUITE(Module, Name, _rt, Profile, X, Y, StdBody, CcmRtBody)) \ + CCMATH_BENCH_IF_RT(CCMATH_BENCH_REGISTER_BINARY_PROFILE_SUITE(Module, Name, _rt, Profile)) + +#define CCMATH_BENCH_DECLARE_UNARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_UNARY_SUITE(Module, Name, , StdBody, CcmBody) + +#define CCMATH_BENCH_REGISTER_UNARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_UNARY_SUITE(Module, Name, ) + +#define CCMATH_BENCH_DECLARE_BINARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_BINARY_SUITE(Module, Name, , StdBody, CcmBody) + +#define CCMATH_BENCH_REGISTER_BINARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_BINARY_SUITE(Module, Name, ) + +#define CCMATH_BENCH_DECLARE_TERNARY_COMPARE(Module, Name, StdBody, CcmBody) CCMATH_BENCH_DECLARE_TERNARY_SUITE(Module, Name, , StdBody, CcmBody) + +#define CCMATH_BENCH_REGISTER_TERNARY_COMPARE(Module, Name) CCMATH_BENCH_REGISTER_TERNARY_SUITE(Module, Name, ) diff --git a/benchmarks/src/math/basic/abs.bench.cpp b/benchmarks/src/math/basic/abs.bench.cpp new file mode 100644 index 00000000..23a14e42 --- /dev/null +++ b/benchmarks/src/math/basic/abs.bench.cpp @@ -0,0 +1,9 @@ +#include "shared/register.hpp" + +#include + +#include + +CCMATH_BENCH_UNARY_COMPARE(basic, abs, return std::abs(x);, return ccm::abs(x);) + +BENCHMARK_MAIN(); diff --git a/benchmarks/src/math/basic/fdim.bench.cpp b/benchmarks/src/math/basic/fdim.bench.cpp new file mode 100644 index 00000000..be342109 --- /dev/null +++ b/benchmarks/src/math/basic/fdim.bench.cpp @@ -0,0 +1,9 @@ +#include "shared/register.hpp" + +#include + +#include + +CCMATH_BENCH_BINARY_COMPARE(basic, fdim, return std::fdim(x, y);, return ccm::fdim(x, y);) + +BENCHMARK_MAIN(); diff --git a/benchmarks/src/math/basic/fma.bench.cpp b/benchmarks/src/math/basic/fma.bench.cpp new file mode 100644 index 00000000..ed019cf0 --- /dev/null +++ b/benchmarks/src/math/basic/fma.bench.cpp @@ -0,0 +1,9 @@ +#include "shared/register.hpp" + +#include + +#include + +CCMATH_BENCH_TERNARY_COMPARE(basic, fma, return std::fma(x, y, z);, return ccm::fma(x, y, z);) + +BENCHMARK_MAIN(); diff --git a/benchmarks/src/math/nearest/trunc.bench.cpp b/benchmarks/src/math/nearest/trunc.bench.cpp new file mode 100644 index 00000000..0359307b --- /dev/null +++ b/benchmarks/src/math/nearest/trunc.bench.cpp @@ -0,0 +1,9 @@ +#include "shared/register.hpp" + +#include + +#include + +CCMATH_BENCH_UNARY_COMPARE(nearest, trunc, return std::trunc(x);, return ccm::trunc(x);) + +BENCHMARK_MAIN(); diff --git a/benchmarks/src/math/power/sqrt.bench.cpp b/benchmarks/src/math/power/sqrt.bench.cpp new file mode 100644 index 00000000..be00db56 --- /dev/null +++ b/benchmarks/src/math/power/sqrt.bench.cpp @@ -0,0 +1,20 @@ +#include "shared/register.hpp" + +#include + +#include + +namespace +{ + inline double sqrt_rt(double v) + { + return ccm::sqrt(v); + } +} // namespace + +CCMATH_BENCH_UNARY_COMPARE_PATHS( + power, sqrt, return std::sqrt(static_cast(x)), return ccm::sqrt(static_cast(x)), return sqrt_rt(static_cast(x))) + +CCMATH_BENCH_UNARY_PROFILE_PATHS(power, sqrt, positive_finite_general, 4.0, return std::sqrt(x), return ccm::sqrt(x), return sqrt_rt(x)) + +BENCHMARK_MAIN(); diff --git a/benchmarks/tools/plot.py b/benchmarks/tools/plot.py deleted file mode 100644 index 78faaeab..00000000 --- a/benchmarks/tools/plot.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python -"""Script to visualize google-benchmark output""" -import argparse -import sys -import logging -import json -import pandas as pd -import matplotlib.pyplot as plt -import pathlib - -# Borrowed from: https://github.com/lakshayg/google_benchmark_plot/blob/master/plot.py - -logging.basicConfig(format="[%(levelname)s] %(message)s") - -METRICS = [ - "real_time", - "cpu_time", - "bytes_per_second", - "items_per_second", - "iterations", -] -TRANSFORMS = {"": lambda x: x, "inverse": lambda x: 1.0 / x} - - -def get_default_ylabel(args): - """Compute default ylabel for commandline args""" - label = "" - if args.transform == "": - label = args.metric - else: - label = args.transform + "(" + args.metric + ")" - if args.relative_to is not None: - label += " relative to %s" % args.relative_to - return label - - -def parse_args(): - """Parse commandline arguments""" - parser = argparse.ArgumentParser(description="Visualize google-benchmark output") - parser.add_argument( - "-f", - metavar="FILE", - type=argparse.FileType("r"), - default=sys.stdin, - dest="file", - help="path to file containing the csv or json benchmark data", - ) - parser.add_argument( - "-m", - metavar="METRIC", - choices=METRICS, - default=METRICS[0], - dest="metric", - help="metric to plot on the y-axis, valid choices are: %s" % ", ".join(METRICS), - ) - parser.add_argument( - "-t", - metavar="TRANSFORM", - choices=TRANSFORMS.keys(), - default="", - help="transform to apply to the chosen metric, valid choices are: %s" - % ", ".join(list(TRANSFORMS)), - dest="transform", - ) - parser.add_argument( - "-r", - metavar="RELATIVE_TO", - type=str, - default=None, - dest="relative_to", - help="plot metrics relative to this label", - ) - parser.add_argument( - "--xlabel", type=str, default="input size", help="label of the x-axis" - ) - parser.add_argument("--ylabel", type=str, help="label of the y-axis") - parser.add_argument("--title", type=str, default="", help="title of the plot") - parser.add_argument( - "--logx", action="store_true", help="plot x-axis on a logarithmic scale" - ) - parser.add_argument( - "--logy", action="store_true", help="plot y-axis on a logarithmic scale" - ) - parser.add_argument( - "--output", type=str, default="", help="File in which to save the graph" - ) - - args = parser.parse_args() - if args.ylabel is None: - args.ylabel = get_default_ylabel(args) - return args - - -def parse_input_size(name): - splits = name.split("/") - if len(splits) == 1: - return 1 - return int(splits[1]) - - -def read_data(args): - """Read and process dataframe using commandline args""" - extension = pathlib.Path(args.file.name).suffix - try: - if extension == ".csv": - data = pd.read_csv(args.file, usecols=["name", args.metric]) - elif extension == ".json": - json_data = json.load(args.file) - data = pd.DataFrame(json_data["benchmarks"]) - else: - logging.error("Unsupported file extension '{}'".format(extension)) - exit(1) - except ValueError: - logging.error( - 'Could not parse the benchmark data. Did you forget "--benchmark_format=[csv|json] when running the benchmark"?' - ) - exit(1) - data["label"] = data["name"].apply(lambda x: x.split("/")[0]) - data["input"] = data["name"].apply(parse_input_size) - data[args.metric] = data[args.metric].apply(TRANSFORMS[args.transform]) - return data - - -def plot_groups(label_groups, args): - """Display the processed data""" - for label, group in label_groups.items(): - plt.plot(group["input"], group[args.metric], label=label, marker=".") - if args.logx: - plt.xscale("log") - if args.logy: - plt.yscale("log") - plt.xlabel(args.xlabel) - plt.ylabel(args.ylabel) - plt.title(args.title) - plt.legend() - if args.output: - logging.info("Saving to %s" % args.output) - plt.savefig(args.output) - else: - plt.show() - - -def main(): - """Entry point of the program""" - args = parse_args() - data = read_data(args) - label_groups = {} - for label, group in data.groupby("label"): - label_groups[label] = group.set_index("input", drop=False) - if args.relative_to is not None: - try: - baseline = label_groups[args.relative_to][args.metric].copy() - except KeyError as key: - msg = "Key %s is not present in the benchmark output" - logging.error(msg, str(key)) - exit(1) - - if args.relative_to is not None: - for label in label_groups: - label_groups[label][args.metric] /= baseline - plot_groups(label_groups, args) - - -if __name__ == "__main__": - main() diff --git a/cmake/README.md b/cmake/README.md new file mode 100644 index 00000000..f5d14783 --- /dev/null +++ b/cmake/README.md @@ -0,0 +1,41 @@ +# CMake tooling + +Helpers in cmake/helpers/ and module lists in cmake/config/. Family CMakeLists.txt files include a registry and call ccmath_add_* helpers instead of listing every source file. + +## Registries + +| Registry | Helper | Wired from | +| --- | --- | --- | +| TestModuleRegistry.cmake | CcmTestHelpers.cmake | tests/unit/CMakeLists.txt | +| OracleCampaignRegistry.cmake | CcmOracleHelpers.cmake | tests/rigorous/CMakeLists.txt | +| FuzzModuleRegistry.cmake | CcmFuzzHelpers.cmake | fuzzing/CMakeLists.txt | +| BenchmarkModuleRegistry.cmake | CcmBenchHelpers.cmake | benchmarks/CMakeLists.txt | +| ExampleModuleRegistry.cmake | CcmExampleHelpers.cmake | examples/CMakeLists.txt | + +TestCtestTargets.cmake defines ccmath-ctest-simple and ccmath-ctest-rigorous. TestOracleTargets.cmake links MPFR and CORE-MATH. cmake/modules/CcmLibFuzzer.cmake probes libFuzzer. + +## Family docs + +| Area | README | +| --- | --- | +| GTest suites | tests/README.md | +| Rigorous wiring | tests/rigorous/README.md | +| Proof smoke | tests/proofs/README.md | +| Oracle campaigns | tests/shared/oracle/README.md | +| Fuzz | fuzzing/README.md | +| Benchmarks | benchmarks/README.md | +| Examples | examples/README.md | + +## Helpers (quick ref) + +CcmTestHelpers.cmake needs CCMATH_TESTS_ROOT set first (tests/CMakeLists.txt does this). ccmath_add_unit_module globs tests/src/math//. ccmath_add_conformance_suite and ccmath_add_rigorous_module_suite cover rigorous GTest subsets. + +CcmFuzzHelpers.cmake needs CCMATH_FUZZ_ROOT. ccmath_add_fuzz_module builds the executable, seeds, smoke CTest, and campaign target. + +CcmBenchHelpers.cmake needs CCMATH_BENCH_ROOT. ccmath_apply_bench_registry loops BenchmarkModuleRegistry.cmake. + +CcmExampleHelpers.cmake needs CCMATH_EXAMPLE_ROOT. ccmath_apply_example_registry loops ExampleModuleRegistry.cmake. + +CcmOracleHelpers.cmake: ccmath_apply_oracle_registries(LOG_DIR) reads OracleCampaignRegistry.cmake. + +Dedicated targets stay in the family CMakeLists.txt when a source file needs its own COMPILE_DEFINITIONS or must be excluded from a glob. diff --git a/cmake/config/BenchmarkModuleRegistry.cmake b/cmake/config/BenchmarkModuleRegistry.cmake new file mode 100644 index 00000000..10fe5ac5 --- /dev/null +++ b/cmake/config/BenchmarkModuleRegistry.cmake @@ -0,0 +1,16 @@ +# Declarative benchmark registry. +# Add a function: drop benchmarks/src/math//.bench.cpp and append below. + +set(CCMATH_BENCH_MODULE_ORDER basic power nearest compare) + +set(CCMATH_BENCH_MODULE_basic_FUNCTIONS abs fdim fma) +set(CCMATH_BENCH_MODULE_basic_OPTION CCM_BENCH_BASIC) + +set(CCMATH_BENCH_MODULE_power_FUNCTIONS sqrt) +set(CCMATH_BENCH_MODULE_power_OPTION CCM_BENCH_POWER) + +set(CCMATH_BENCH_MODULE_nearest_FUNCTIONS trunc) +set(CCMATH_BENCH_MODULE_nearest_OPTION CCM_BENCH_NEAREST) + +set(CCMATH_BENCH_MODULE_compare_FUNCTIONS) +set(CCMATH_BENCH_MODULE_compare_OPTION CCM_BENCH_COMPARE) diff --git a/cmake/config/BuildManifest.cmake b/cmake/config/BuildManifest.cmake new file mode 100644 index 00000000..874cf195 --- /dev/null +++ b/cmake/config/BuildManifest.cmake @@ -0,0 +1,132 @@ +# Library-facing build metadata for CMake, manifest emission, and secondary build codegen. + +set(CCMATH_LIBRARY_MANIFEST_CPP_STANDARD 17) + +set(CCMATH_LIBRARY_MANIFEST_GENERATED_HEADERS + ccmath/version.hpp +) + +set(CCMATH_LIBRARY_MANIFEST_VERSION_TEMPLATE cmake/in/version.hpp.in) + +# Ordered option keys. Each key sets CCMATH_LIBRARY_MANIFEST_OPTION__* variables. +set(CCMATH_LIBRARY_MANIFEST_OPTION_KEYS + runtime_simd + disable_errno + disable_fenv + freestanding + reduced_precision_powl + deterministic +) + +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_CMAKE_VAR CCMATH_DISABLE_RUNTIME_SIMD) +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_CMAKE_INVERT TRUE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_MESON_OPTION enable_runtime_simd) +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_DEFINE CCM_CONFIG_USE_RT_SIMD) +set(CCMATH_LIBRARY_MANIFEST_OPTION_runtime_simd_DESCRIPTION + "Enable SIMD optimization for runtime evaluation (does not affect compile-time)") + +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_CMAKE_VAR CCMATH_DISABLE_ERRNO) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_MESON_OPTION disable_errno) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_DEFINE CCM_CONFIG_DISABLE_ERRNO) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_errno_DESCRIPTION + "Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)") + +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_VAR CCMATH_DISABLE_FENV) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_MESON_OPTION disable_fenv) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DEFINE CCM_CONFIG_DISABLE_FENV) +set(CCMATH_LIBRARY_MANIFEST_OPTION_disable_fenv_DESCRIPTION + "Completely disable the host floating-point environment: drop every and include, assume round-to-nearest at runtime, and signal no fp-exceptions (auto-enabled when no host fenv header exists)") + +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_VAR CCMATH_FREESTANDING) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_MESON_OPTION freestanding) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_DEFINE CCM_CONFIG_FREESTANDING) +set(CCMATH_LIBRARY_MANIFEST_OPTION_freestanding_DESCRIPTION + "Build for a freestanding C++ environment: restrict to freestanding-conformant headers, dropping the test-only path in the runtime SIMD layer and the MSVC system-math path (auto-enabled when __STDC_HOSTED__ is 0)") + +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_VAR CCMATH_DISABLE_REDUCED_PRECISION_POWL) +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_MESON_OPTION disable_reduced_precision_powl) +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_DEFINE CCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL) +set(CCMATH_LIBRARY_MANIFEST_OPTION_reduced_precision_powl_DESCRIPTION + "Return quiet NaN from powl on non-binary80 long double instead of the default reduced-precision double fallback") + +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_CMAKE_VAR CCMATH_ENABLE_DETERMINISTIC) +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_CMAKE_DEFAULT OFF) +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_CMAKE_INVERT FALSE) +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_MESON_OPTION deterministic) +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_DEFINE CCM_CONFIG_DETERMINISTIC) +set(CCMATH_LIBRARY_MANIFEST_OPTION_deterministic_DESCRIPTION + "Produce bit-identical cross-hardware math: route transcendentals through the generic kernels (no libm), force the correctly-rounded FMA path, disable runtime SIMD, and evaluate long double in double precision (GCC/Clang)") + +function(ccmath_manifest_declare_library_options) + foreach (_ccmath_manifest_key IN LISTS CCMATH_LIBRARY_MANIFEST_OPTION_KEYS) + set(_ccmath_cmake_var "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_VAR}") + set(_ccmath_description "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_DESCRIPTION}") + set(_ccmath_default "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_DEFAULT}") + option("${_ccmath_cmake_var}" "${_ccmath_description}" "${_ccmath_default}") + endforeach () +endfunction() + +function(ccmath_manifest_apply_library_compile_definitions target) + foreach (_ccmath_manifest_key IN LISTS CCMATH_LIBRARY_MANIFEST_OPTION_KEYS) + set(_ccmath_cmake_var "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_VAR}") + set(_ccmath_define "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_DEFINE}") + set(_ccmath_invert "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_INVERT}") + + if (_ccmath_invert) + if (NOT ${_ccmath_cmake_var}) + target_compile_definitions("${target}" INTERFACE "${_ccmath_define}") + endif () + else () + if (${_ccmath_cmake_var}) + target_compile_definitions("${target}" INTERFACE "${_ccmath_define}") + endif () + endif () + endforeach () +endfunction() + +function(ccmath_manifest_meson_default option_key) + set(_ccmath_cmake_default "${CCMATH_LIBRARY_MANIFEST_OPTION_${option_key}_CMAKE_DEFAULT}") + set(_ccmath_invert "${CCMATH_LIBRARY_MANIFEST_OPTION_${option_key}_CMAKE_INVERT}") + + if (_ccmath_invert) + if (_ccmath_cmake_default) + set(_ccmath_meson_default false PARENT_SCOPE) + else () + set(_ccmath_meson_default true PARENT_SCOPE) + endif () + else () + if (_ccmath_cmake_default) + set(_ccmath_meson_default true PARENT_SCOPE) + else () + set(_ccmath_meson_default false PARENT_SCOPE) + endif () + endif () +endfunction() + +function(ccmath_manifest_option_enabled option_key out_var) + set(_ccmath_cmake_var "${CCMATH_LIBRARY_MANIFEST_OPTION_${option_key}_CMAKE_VAR}") + set(_ccmath_invert "${CCMATH_LIBRARY_MANIFEST_OPTION_${option_key}_CMAKE_INVERT}") + + if (_ccmath_invert) + if (NOT ${_ccmath_cmake_var}) + set("${out_var}" TRUE PARENT_SCOPE) + else () + set("${out_var}" FALSE PARENT_SCOPE) + endif () + else () + if (${_ccmath_cmake_var}) + set("${out_var}" TRUE PARENT_SCOPE) + else () + set("${out_var}" FALSE PARENT_SCOPE) + endif () + endif () +endfunction() diff --git a/cmake/config/EmitBuildManifest.cmake b/cmake/config/EmitBuildManifest.cmake new file mode 100644 index 00000000..83d87932 --- /dev/null +++ b/cmake/config/EmitBuildManifest.cmake @@ -0,0 +1,148 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/BuildManifest.cmake") + +function(_ccmath_manifest_json_escape input output_var) + string(REPLACE "\\" "\\\\" _ccmath_escaped "${input}") + string(REPLACE "\"" "\\\"" _ccmath_escaped "${_ccmath_escaped}") + set("${output_var}" "${_ccmath_escaped}" PARENT_SCOPE) +endfunction() + +function(_ccmath_manifest_append_option_json_lines out_var) + set(_ccmath_json_lines "") + set(_ccmath_comma "") + + foreach (_ccmath_manifest_key IN LISTS CCMATH_LIBRARY_MANIFEST_OPTION_KEYS) + set(_ccmath_cmake_var "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_VAR}") + set(_ccmath_meson_option "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_MESON_OPTION}") + set(_ccmath_define "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_DEFINE}") + set(_ccmath_description "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_DESCRIPTION}") + set(_ccmath_cmake_default "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_DEFAULT}") + set(_ccmath_invert "${CCMATH_LIBRARY_MANIFEST_OPTION_${_ccmath_manifest_key}_CMAKE_INVERT}") + + ccmath_manifest_meson_default("${_ccmath_manifest_key}" _ccmath_meson_default) + ccmath_manifest_option_enabled("${_ccmath_manifest_key}" _ccmath_enabled) + + _ccmath_manifest_json_escape("${_ccmath_description}" _ccmath_description_json) + + if (_ccmath_cmake_default) + set(_ccmath_cmake_default_json "true") + else () + set(_ccmath_cmake_default_json "false") + endif () + + if (_ccmath_meson_default) + set(_ccmath_meson_default_json "true") + else () + set(_ccmath_meson_default_json "false") + endif () + + if (_ccmath_invert) + set(_ccmath_invert_json "true") + else () + set(_ccmath_invert_json "false") + endif () + + if (_ccmath_enabled) + set(_ccmath_enabled_json "true") + else () + set(_ccmath_enabled_json "false") + endif () + + string(APPEND _ccmath_json_lines + "${_ccmath_comma} {\n" + " \"key\": \"${_ccmath_manifest_key}\",\n" + " \"cmake_option\": \"${_ccmath_cmake_var}\",\n" + " \"meson_option\": \"${_ccmath_meson_option}\",\n" + " \"define\": \"${_ccmath_define}\",\n" + " \"description\": \"${_ccmath_description_json}\",\n" + " \"cmake_default\": ${_ccmath_cmake_default_json},\n" + " \"meson_default\": ${_ccmath_meson_default_json},\n" + " \"cmake_invert\": ${_ccmath_invert_json},\n" + " \"enabled\": ${_ccmath_enabled_json}\n" + " }") + set(_ccmath_comma ",\n") + endforeach () + + set("${out_var}" "${_ccmath_json_lines}" PARENT_SCOPE) +endfunction() + +function(ccmath_emit_build_manifest output_path) + _ccmath_manifest_append_option_json_lines(_ccmath_option_json) + + set(_ccmath_generated_headers_json "") + set(_ccmath_header_comma "") + foreach (_ccmath_header IN LISTS CCMATH_LIBRARY_MANIFEST_GENERATED_HEADERS) + string(APPEND _ccmath_generated_headers_json + "${_ccmath_header_comma} \"${_ccmath_header}\"") + set(_ccmath_header_comma ",\n") + endforeach () + + _ccmath_manifest_json_escape("${CMAKE_CURRENT_SOURCE_DIR}" _ccmath_source_dir_json) + _ccmath_manifest_json_escape("${CMAKE_CURRENT_BINARY_DIR}" _ccmath_build_dir_json) + _ccmath_manifest_json_escape("${CCMATH_LIBRARY_MANIFEST_VERSION_TEMPLATE}" _ccmath_version_template_json) + + set(_ccmath_manifest_json "") + string(APPEND _ccmath_manifest_json + "{\n" + " \"name\": \"${CCMATH_TARGET_NAME}\",\n" + " \"version\": \"${CCMATH_BUILD_VERSION}\",\n" + " \"cpp_standard\": ${CCMATH_LIBRARY_MANIFEST_CPP_STANDARD},\n" + " \"source_dir\": \"${_ccmath_source_dir_json}\",\n" + " \"build_dir\": \"${_ccmath_build_dir_json}\",\n" + " \"include_dirs\": [\n" + " \"include\",\n" + " \"${CMAKE_CURRENT_BINARY_DIR}/include\"\n" + " ],\n" + " \"version_template\": \"${_ccmath_version_template_json}\",\n" + " \"generated_headers\": [\n" + "${_ccmath_generated_headers_json}\n" + " ],\n" + " \"options\": [\n" + "${_ccmath_option_json}\n" + " ]\n" + "}\n") + + file(WRITE "${output_path}" "${_ccmath_manifest_json}") +endfunction() + +function(ccmath_setup_secondary_build_generation) + set(_ccmath_manifest_path "${CMAKE_CURRENT_BINARY_DIR}/ccmath-build-manifest.json") + set(_ccmath_gen_dir "${CMAKE_CURRENT_BINARY_DIR}/gen-secondary") + + ccmath_emit_build_manifest("${_ccmath_manifest_path}") + + find_package(Python3 COMPONENTS Interpreter QUIET) + if (NOT Python3_Interpreter_FOUND) + message(WARNING "CCMath: Python3 not found. Secondary build generation will be skipped.") + return() + endif () + + add_custom_command( + OUTPUT + "${_ccmath_gen_dir}/meson.build" + "${_ccmath_gen_dir}/meson_options.txt" + "${_ccmath_gen_dir}/premake5.lua" + "${_ccmath_gen_dir}/BUILD_STAMP" + COMMAND ${CMAKE_COMMAND} -E make_directory "${_ccmath_gen_dir}" + COMMAND ${CMAKE_COMMAND} -E copy "${_ccmath_manifest_path}" "${_ccmath_gen_dir}/ccmath-build-manifest.json" + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/gen_secondary_builds.py" + --manifest "${_ccmath_manifest_path}" + --output-dir "${_ccmath_gen_dir}" + --source-dir "${CMAKE_CURRENT_SOURCE_DIR}" + --update-root + COMMAND ${CMAKE_COMMAND} -E touch "${_ccmath_gen_dir}/BUILD_STAMP" + DEPENDS + "${_ccmath_manifest_path}" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/gen_secondary_builds.py" + COMMENT "Generating Meson and Premake secondary build files" + VERBATIM + ) + + add_custom_target( + ccmath-generate-secondary-builds + ALL + DEPENDS + "${_ccmath_gen_dir}/BUILD_STAMP" + ) +endfunction() diff --git a/cmake/config/ExampleModuleRegistry.cmake b/cmake/config/ExampleModuleRegistry.cmake new file mode 100644 index 00000000..d2990af5 --- /dev/null +++ b/cmake/config/ExampleModuleRegistry.cmake @@ -0,0 +1,12 @@ +# Declarative example registry. +# Add an example: drop examples/src/.cpp and append below. + +set(CCMATH_EXAMPLE_MODULE_ORDER smoke basic trig power expo nearest compare) + +set(CCMATH_EXAMPLE_MODULE_smoke_EXAMPLES constexpr_smoke) +set(CCMATH_EXAMPLE_MODULE_basic_EXAMPLES constexpr_abs signed_magnitude) +set(CCMATH_EXAMPLE_MODULE_trig_EXAMPLES unit_circle) +set(CCMATH_EXAMPLE_MODULE_power_EXAMPLES hypot_distance) +set(CCMATH_EXAMPLE_MODULE_expo_EXAMPLES decibel_gain) +set(CCMATH_EXAMPLE_MODULE_nearest_EXAMPLES rounding_edge_cases) +set(CCMATH_EXAMPLE_MODULE_compare_EXAMPLES fp_classes) diff --git a/cmake/config/FindCoreMath.cmake b/cmake/config/FindCoreMath.cmake new file mode 100644 index 00000000..870eaf4c --- /dev/null +++ b/cmake/config/FindCoreMath.cmake @@ -0,0 +1,84 @@ +# Discover CORE-MATH oracle library for rigorous validation. +# +# Resolution order: +# 1. pkg-config module coremath or crmath +# 2. CCMATH_COREMATH_ROOT or CMAKE_PREFIX_PATH (crmath.h + libcoremath_oracle.a / libcrmath.a) +# 3. CCMATH_FETCH_COREMATH=ON FetchContent fallback + +include_guard(GLOBAL) + +set(CCMATH_COREMATH_FOUND FALSE) + +if (CCMATH_COREMATH_ROOT) + list(APPEND CMAKE_PREFIX_PATH "${CCMATH_COREMATH_ROOT}") +endif () + +find_package(PkgConfig QUIET) +if (PkgConfig_FOUND) + foreach (_pc_module IN ITEMS coremath crmath ccmath_coremath_oracle) + pkg_check_modules(_COREMATH_PC QUIET IMPORTED_TARGET ${_pc_module}) + if (TARGET PkgConfig::_COREMATH_PC) + if (NOT TARGET ccmath::coremath_oracle) + add_library(ccmath::coremath_oracle ALIAS PkgConfig::_COREMATH_PC) + endif () + set(CCMATH_COREMATH_FOUND TRUE) + break() + endif () + unset(_COREMATH_PC_LIBRARIES) + unset(_COREMATH_PC_INCLUDE_DIRS) + unset(_COREMATH_PC_CFLAGS) + unset(_COREMATH_PC_CFLAGS_OTHER) + endforeach () +endif () + +if (NOT CCMATH_COREMATH_FOUND) + find_path(CCMATH_COREMATH_INCLUDE_DIR + NAMES crmath.h coremath.h + HINTS ${CCMATH_COREMATH_ROOT} + PATH_SUFFIXES include + ) + find_library(CCMATH_COREMATH_LIBRARY + NAMES coremath_oracle crmath coremath + HINTS ${CCMATH_COREMATH_ROOT} + PATH_SUFFIXES lib lib64 + ) + + if (CCMATH_COREMATH_INCLUDE_DIR AND CCMATH_COREMATH_LIBRARY) + if (NOT TARGET ccmath_coremath_oracle) + add_library(ccmath_coremath_oracle UNKNOWN IMPORTED GLOBAL) + set_target_properties(ccmath_coremath_oracle PROPERTIES + IMPORTED_LOCATION "${CCMATH_COREMATH_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${CCMATH_COREMATH_INCLUDE_DIR}" + ) + add_library(ccmath::coremath_oracle ALIAS ccmath_coremath_oracle) + endif () + set(CCMATH_COREMATH_FOUND TRUE) + endif () +endif () + +if (NOT CCMATH_COREMATH_FOUND AND CCMATH_FETCH_COREMATH) + include(FetchContent) + FetchContent_Declare( + coremath_upstream + GIT_REPOSITORY https://github.com/mockingbirdnest/core-math.git + GIT_TAG master + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(coremath_upstream) + + set(CORE_MATH_SOURCE_DIR "${coremath_upstream_SOURCE_DIR}" CACHE PATH "" FORCE) + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../coremath ${CMAKE_BINARY_DIR}/_deps/coremath_oracle_build EXCLUDE_FROM_ALL) + + if (TARGET coremath_oracle AND NOT TARGET ccmath::coremath_oracle) + add_library(ccmath::coremath_oracle ALIAS coremath_oracle) + set(CCMATH_COREMATH_FOUND TRUE) + endif () +endif () + +if (CCMATH_ENABLE_COREMATH_TESTS AND NOT CCMATH_COREMATH_FOUND) + if (CCMATH_FETCH_COREMATH) + message(STATUS "CCMath rigorous CORE-MATH validation requested, but CORE-MATH was not found and FetchContent did not produce a target") + else () + message(STATUS "CCMath rigorous CORE-MATH validation requested, but CORE-MATH was not found (set CCMATH_COREMATH_ROOT or CCMATH_FETCH_COREMATH=ON)") + endif () +endif () diff --git a/cmake/config/FindMpfrGmp.cmake b/cmake/config/FindMpfrGmp.cmake new file mode 100644 index 00000000..23c4cdda --- /dev/null +++ b/cmake/config/FindMpfrGmp.cmake @@ -0,0 +1,62 @@ +# Discover MPFR and GMP for rigorous oracle validation. +# +# Linux and macOS typically use system pkg-config modules. +# Windows CI uses vcpkg (see cmake/vcpkg/vcpkg.json) with pkgconf and .pc files. + +include_guard(GLOBAL) + +set(CCMATH_MPFR_FOUND FALSE) + +# vcpkg pkg-config wiring is Windows-only. This is a function (not a macro) so the early +# returns scope to the helper instead of aborting the whole module on Linux and macOS. +function(_ccmath_configure_vcpkg_pkgconfig) + if (NOT WIN32) + return() + endif () + if (NOT CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg") + return() + endif () + if (NOT DEFINED VCPKG_INSTALLED_DIR OR NOT DEFINED VCPKG_TARGET_TRIPLET) + return() + endif () + + set(_vcpkg_triplet_root "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}") + find_program(CCMATH_PKGCONFIG_EXECUTABLE + NAMES pkgconf pkg-config + PATHS + "${_vcpkg_triplet_root}/tools/pkgconf" + "${_vcpkg_triplet_root}/tools/pkg-config" + PATH_SUFFIXES bin + NO_DEFAULT_PATH + ) + if (CCMATH_PKGCONFIG_EXECUTABLE) + set(PKG_CONFIG_EXECUTABLE "${CCMATH_PKGCONFIG_EXECUTABLE}" CACHE FILEPATH "" FORCE) + endif () + + set(ENV{PKG_CONFIG_PATH} + "${_vcpkg_triplet_root}/lib/pkgconfig;${_vcpkg_triplet_root}/share/pkgconfig") +endfunction() + +_ccmath_configure_vcpkg_pkgconfig() + +find_package(PkgConfig QUIET) +if (PkgConfig_FOUND) + pkg_check_modules(MPFR QUIET IMPORTED_TARGET mpfr) + pkg_check_modules(GMP QUIET IMPORTED_TARGET gmp) +endif () + +if (TARGET PkgConfig::MPFR AND TARGET PkgConfig::GMP) + if (NOT TARGET ccmath_mpfr_oracle) + add_library(ccmath_mpfr_oracle INTERFACE IMPORTED GLOBAL) + target_link_libraries(ccmath_mpfr_oracle INTERFACE PkgConfig::MPFR PkgConfig::GMP) + add_library(ccmath::mpfr_oracle ALIAS ccmath_mpfr_oracle) + endif () + set(CCMATH_MPFR_FOUND TRUE) +endif () + +if (CCMATH_ENABLE_MPFR_TESTS AND NOT CCMATH_MPFR_FOUND) + # Fail loudly: silently dropping the requested oracle suite lets CI pass without validation. + message(FATAL_ERROR + "CCMATH_ENABLE_MPFR_TESTS=ON but MPFR/GMP were not both found via pkg-config. " + "Install mpfr and gmp (and pkg-config), or set CCMATH_ENABLE_MPFR_TESTS=OFF.") +endif () diff --git a/cmake/config/FuzzModuleRegistry.cmake b/cmake/config/FuzzModuleRegistry.cmake new file mode 100644 index 00000000..133d082d --- /dev/null +++ b/cmake/config/FuzzModuleRegistry.cmake @@ -0,0 +1,13 @@ +# Declarative fuzz module registry. +# Add a family: drop fuzzing/src/math/_fuzz.cpp and append MODULE below. + +set(CCMATH_FUZZ_MODULE_ORDER + basic + compare + expo + fmanip + nearest + power + trig + misc +) diff --git a/cmake/config/OracleCampaignRegistry.cmake b/cmake/config/OracleCampaignRegistry.cmake new file mode 100644 index 00000000..be660ea9 --- /dev/null +++ b/cmake/config/OracleCampaignRegistry.cmake @@ -0,0 +1,192 @@ +# Declarative rigorous oracle registry. +# Add an executable: set TARGET and SOURCE, append ID to CCMATH_ORACLE__EXECUTABLE_IDS. +# Add ctests: append test ID to CCMATH_ORACLE___CTESTS and define CCMATH_ORACLE__TEST__*. + +set(CCMATH_ORACLE_MPFR_EXECUTABLE_IDS + mpfr_pow_double + mpfr_pow_float + mpfr_fma_double + mpfr_fmaf + mpfr_powl + powl_characterization + pow_search + cross_libm_pow + mpfr_unary +) + +set(CCMATH_ORACLE_MPFR_mpfr_pow_double_TARGET ccmath-rigorous-mpfr-pow) +set(CCMATH_ORACLE_MPFR_mpfr_pow_double_SOURCE ../shared/oracle/mpfr_pow_double.cpp) +set(CCMATH_ORACLE_MPFR_mpfr_pow_double_CTESTS mpfr_pow_double_public mpfr_pow_double_nobuiltin) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_NAME ccmath-rigorous-mpfr-pow) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_TARGET ccmath-rigorous-mpfr-pow) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_REQUIRES "NOT CCMATH_TEST_DISABLE_RUNTIME_BUILTIN") +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_TIMEOUT 600) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_ARGS --mode=quick --path=public_default) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_public_LOG_OUTPUT mpfr-pow-double-public_default.json) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_NAME ccmath-rigorous-mpfr-pow-nobuiltin) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_TARGET ccmath-rigorous-mpfr-pow) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_REQUIRES CCMATH_TEST_DISABLE_RUNTIME_BUILTIN) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_TIMEOUT 600) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_ARGS --mode=quick --path=runtime_no_builtin,generic_modeled_domain) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_double_nobuiltin_LOG_OUTPUT mpfr-pow-double-nobuiltin.json) + +set(CCMATH_ORACLE_MPFR_mpfr_pow_float_TARGET ccmath-rigorous-mpfr-powf) +set(CCMATH_ORACLE_MPFR_mpfr_pow_float_SOURCE ../shared/oracle/mpfr_pow_float.cpp) +set(CCMATH_ORACLE_MPFR_mpfr_pow_float_CTESTS mpfr_pow_float_public mpfr_pow_float_reduced mpfr_pow_float_nobuiltin) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_NAME ccmath-rigorous-mpfr-powf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_TARGET ccmath-rigorous-mpfr-powf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_TIMEOUT 900) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_ARGS --mode=quick --path=public_default) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_public_LOG_OUTPUT mpfr-powf-public_default.json) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_NAME ccmath-rigorous-mpfr-powf-reduced) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_TARGET ccmath-rigorous-mpfr-powf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_TIMEOUT 900) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_ARGS --mode=quick --path=public_default --domain=mantissa-sweep,subnormal-x) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_reduced_LOG_OUTPUT mpfr-powf-reduced-domains.json) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_NAME ccmath-rigorous-mpfr-powf-nobuiltin) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_TARGET ccmath-rigorous-mpfr-powf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_REQUIRES CCMATH_TEST_DISABLE_RUNTIME_BUILTIN) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_TIMEOUT 900) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_ARGS --mode=quick --path=runtime_no_builtin,generic_modeled_domain) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_pow_float_nobuiltin_LOG_OUTPUT mpfr-powf-nobuiltin.json) + +set(CCMATH_ORACLE_MPFR_mpfr_fma_double_TARGET ccmath-rigorous-mpfr-fma) +set(CCMATH_ORACLE_MPFR_mpfr_fma_double_SOURCE ../shared/oracle/mpfr_fma_double.cpp) +set(CCMATH_ORACLE_MPFR_mpfr_fma_double_CTESTS mpfr_fma_double) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_NAME ccmath-rigorous-mpfr-fma) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_TARGET ccmath-rigorous-mpfr-fma) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_TIMEOUT 600) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_ARGS --mode=quick --path=public_default,software_fallback,quiet_generic --rounding-modes=all) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fma_double_LOG_OUTPUT mpfr-fma-double.json) + +set(CCMATH_ORACLE_MPFR_mpfr_fmaf_TARGET ccmath-rigorous-mpfr-fmaf) +set(CCMATH_ORACLE_MPFR_mpfr_fmaf_SOURCE ../shared/oracle/mpfr_fma_float.cpp) +set(CCMATH_ORACLE_MPFR_mpfr_fmaf_CTESTS mpfr_fmaf) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_NAME ccmath-rigorous-mpfr-fmaf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_TARGET ccmath-rigorous-mpfr-fmaf) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_TIMEOUT 600) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_ARGS --mode=quick --path=public_default,software_fallback,quiet_generic --rounding-modes=all) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_fmaf_LOG_OUTPUT mpfr-fmaf.json) + +set(CCMATH_ORACLE_MPFR_mpfr_powl_TARGET ccmath-rigorous-mpfr-powl) +set(CCMATH_ORACLE_MPFR_mpfr_powl_SOURCE ../shared/oracle/mpfr_powl.cpp) +set(CCMATH_ORACLE_MPFR_mpfr_powl_CTESTS mpfr_powl) + +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_NAME ccmath-rigorous-mpfr-powl) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_TARGET ccmath-rigorous-mpfr-powl) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_TIMEOUT 300) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_ARGS --mode=quick) +set(CCMATH_ORACLE_MPFR_TEST_mpfr_powl_JSON_OUTPUT mpfr-powl.json) + +set(CCMATH_ORACLE_MPFR_powl_characterization_TARGET ccmath-rigorous-powl-characterization) +set(CCMATH_ORACLE_MPFR_powl_characterization_SOURCE ../shared/oracle/powl_platform_characterization.cpp) +set(CCMATH_ORACLE_MPFR_powl_characterization_CTESTS powl_characterization) + +set(CCMATH_ORACLE_MPFR_TEST_powl_characterization_NAME ccmath-rigorous-powl-characterization) +set(CCMATH_ORACLE_MPFR_TEST_powl_characterization_TARGET ccmath-rigorous-powl-characterization) +set(CCMATH_ORACLE_MPFR_TEST_powl_characterization_TIMEOUT 120) +set(CCMATH_ORACLE_MPFR_TEST_powl_characterization_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_powl_characterization_JSON_OUTPUT powl-platform.json) + +set(CCMATH_ORACLE_MPFR_pow_search_TARGET ccmath-rigorous-pow-search) +set(CCMATH_ORACLE_MPFR_pow_search_SOURCE ../shared/oracle/pow_adversarial_search.cpp) +set(CCMATH_ORACLE_MPFR_pow_search_CTESTS pow_search_smoke) + +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_NAME ccmath-rigorous-pow-search-smoke) +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_TARGET ccmath-rigorous-pow-search) +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_REQUIRES CCMATH_ENABLE_SEARCH_TOOLS) +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_TIMEOUT 60) +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_pow_search_smoke_ARGS --mode=near-one --count=256 --path=public_default) + +set(CCMATH_ORACLE_MPFR_cross_libm_pow_TARGET ccmath-rigorous-cross-libm-pow) +set(CCMATH_ORACLE_MPFR_cross_libm_pow_SOURCE ../shared/oracle/cross_libm_pow.cpp) +set(CCMATH_ORACLE_MPFR_cross_libm_pow_CTESTS cross_libm_pow_smoke) + +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_NAME ccmath-rigorous-cross-libm-pow-smoke) +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_TARGET ccmath-rigorous-cross-libm-pow) +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_REQUIRES CCMATH_ENABLE_CROSS_LIBM_TESTS) +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_TIMEOUT 60) +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_LABELS rigorous mpfr) +set(CCMATH_ORACLE_MPFR_TEST_cross_libm_pow_smoke_ARGS --corpus=quick --format=json --output=cross-libm-pow-quick.json) + +set(CCMATH_ORACLE_MPFR_mpfr_unary_TARGET ccmath-rigorous-mpfr-unary) +set(CCMATH_ORACLE_MPFR_mpfr_unary_SOURCE ../shared/oracle/mpfr_unary.cpp) + +# Sampled, CI-friendly smoke per unary function. The doc-quality numbers (large sampled binary64 +# and exhaustive binary32) come from tools/measure_ulp.sh, not from these ctests, so these stay +# bounded and do not write the canonical summary JSON the doc generator consumes. +set(CCMATH_ORACLE_MPFR_UNARY_DOUBLE_FUNCTIONS + exp exp2 expm1 log log1p log2 log10 sin cos tan asin acos atan cbrt sqrt tgamma lgamma) +set(CCMATH_ORACLE_MPFR_UNARY_FLOAT_FUNCTIONS exp log sin sqrt tgamma) + +set(CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS "") +foreach (_ccm_unary_fn IN LISTS CCMATH_ORACLE_MPFR_UNARY_DOUBLE_FUNCTIONS) + set(_ccm_unary_id unary_${_ccm_unary_fn}_double) + list(APPEND CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS ${_ccm_unary_id}) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_NAME ccmath-rigorous-mpfr-unary-${_ccm_unary_fn}-double) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TARGET ccmath-rigorous-mpfr-unary) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TIMEOUT 300) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_LABELS rigorous mpfr) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_ARGS + --function=${_ccm_unary_fn} --type=double --mode=quick --count=20000 --rounding-modes=all) +endforeach () +foreach (_ccm_unary_fn IN LISTS CCMATH_ORACLE_MPFR_UNARY_FLOAT_FUNCTIONS) + set(_ccm_unary_id unary_${_ccm_unary_fn}_float) + list(APPEND CCMATH_ORACLE_MPFR_mpfr_unary_CTESTS ${_ccm_unary_id}) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_NAME ccmath-rigorous-mpfr-unary-${_ccm_unary_fn}-float) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TARGET ccmath-rigorous-mpfr-unary) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_TIMEOUT 300) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_LABELS rigorous mpfr) + set(CCMATH_ORACLE_MPFR_TEST_${_ccm_unary_id}_ARGS + --function=${_ccm_unary_fn} --type=float --mode=quick --count=20000 --rounding-modes=all) +endforeach () + +set(CCMATH_ORACLE_COREMATH_EXECUTABLE_IDS + coremath_pow_double + coremath_pow_float +) + +set(CCMATH_ORACLE_COREMATH_coremath_pow_double_TARGET ccmath-rigorous-coremath-pow) +set(CCMATH_ORACLE_COREMATH_coremath_pow_double_SOURCE ../shared/oracle/coremath_pow_double.cpp) +set(CCMATH_ORACLE_COREMATH_coremath_pow_double_CTESTS coremath_pow_double) + +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_NAME ccmath-rigorous-coremath-pow) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_TARGET ccmath-rigorous-coremath-pow) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_TIMEOUT 900) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_LABELS rigorous coremath) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_ARGS --mode=quick --path=generic_runtime --rounding-modes=all) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_double_LOG_OUTPUT coremath-pow-double-generic_runtime.json) + +set(CCMATH_ORACLE_COREMATH_coremath_pow_float_TARGET ccmath-rigorous-coremath-powf) +set(CCMATH_ORACLE_COREMATH_coremath_pow_float_SOURCE ../shared/oracle/coremath_pow_float.cpp) +set(CCMATH_ORACLE_COREMATH_coremath_pow_float_CTESTS coremath_pow_float coremath_pow_float_reduced) + +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_NAME ccmath-rigorous-coremath-powf) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_TARGET ccmath-rigorous-coremath-powf) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_TIMEOUT 1200) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_LABELS rigorous coremath) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_ARGS --mode=quick --path=generic_runtime --rounding-modes=all) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_LOG_OUTPUT coremath-powf-generic_runtime.json) + +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_NAME ccmath-rigorous-coremath-powf-reduced) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_TARGET ccmath-rigorous-coremath-powf) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_TIMEOUT 1200) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_LABELS rigorous coremath) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_ARGS --mode=quick --path=generic_runtime --rounding-modes=all --domain=mantissa-sweep,subnormal-x) +set(CCMATH_ORACLE_COREMATH_TEST_coremath_pow_float_reduced_LOG_OUTPUT coremath-powf-reduced-domains.json) diff --git a/cmake/config/ProjectCompileDefinitions.cmake b/cmake/config/ProjectCompileDefinitions.cmake index ee958cfc..6489e6b2 100644 --- a/cmake/config/ProjectCompileDefinitions.cmake +++ b/cmake/config/ProjectCompileDefinitions.cmake @@ -1,7 +1,22 @@ -if (CCMATH_ENABLE_RUNTIME_SIMD) - target_compile_definitions(ccmath INTERFACE CCM_CONFIG_USE_RT_SIMD) +include(cmake/config/BuildManifest.cmake) +ccmath_manifest_apply_library_compile_definitions(ccmath) + +# ccmath is header-only, so the math compiles in the consumer's translation unit. +# Deterministic mode must stop the compiler from contracting stray a*b+c into a +# hardware FMA (which would diverge between FMA and non-FMA targets). The flag has +# to be INTERFACE so it reaches consumers. MSVC does not contract under the default +# /fp:precise, so no MSVC flag is needed. +if (CCMATH_ENABLE_DETERMINISTIC) + target_compile_options(ccmath INTERFACE + $<$>:-ffp-contract=off> + ) endif () -if (CCMATH_DISABLE_ERRNO) - target_compile_definitions(ccmath INTERFACE CCM_CONFIG_DISABLE_ERRNO) -endif () \ No newline at end of file +# On MSVC the runtime path calls the system libm (via ) for the expensive transcendentals, +# which has no foldable __builtin and no SVML, so the generic kernels would otherwise be used and are +# not accurate enough. This is on by default for MSVC. Disabling it keeps the build free of +# and falls back to the generic kernels. +option(CCMATH_DISABLE_SYSTEM_MATH "Do not call the system libm on MSVC (keeps the build free of )" OFF) +if (CCMATH_DISABLE_SYSTEM_MATH) + target_compile_definitions(ccmath INTERFACE CCM_CONFIG_DISABLE_SYSTEM_MATH) +endif () diff --git a/cmake/config/ShowcaseDeps.cmake b/cmake/config/ShowcaseDeps.cmake new file mode 100644 index 00000000..23f88cc6 --- /dev/null +++ b/cmake/config/ShowcaseDeps.cmake @@ -0,0 +1,73 @@ +include_guard(GLOBAL) + +include(FetchContent) + +function(ccmath_showcase_fetch_gcem) + if (NOT CCMATH_SHOWCASE_ENABLE_GCEM) + return() + endif () + if (TARGET showcase::gcem) + return() + endif () + + set(_CCMATH_GCEM_VERSION v1.18.0) + FetchContent_Declare( + gcem + GIT_REPOSITORY https://github.com/kthohr/gcem.git + GIT_TAG ${_CCMATH_GCEM_VERSION} + GIT_SHALLOW TRUE + ) + FetchContent_GetProperties(gcem) + if (NOT gcem_POPULATED) + FetchContent_Populate(gcem) + endif () + + add_library(showcase_gcem INTERFACE) + add_library(showcase::gcem ALIAS showcase_gcem) + target_include_directories(showcase_gcem SYSTEM INTERFACE ${gcem_SOURCE_DIR}/include) +endfunction() + +function(ccmath_showcase_fetch_gtest) + if (TARGET gtest::gtest) + return() + endif () + + set(_CCMATH_GTEST_VERSION v1.15.2) + if (WIN32) + set(gtest_force_shared_crt ON) + set(gtest_disable_pthreads ON) + endif () + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) + set(gtest_build_samples OFF CACHE BOOL "" FORCE) + set(gtest_build_tests OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG ${_CCMATH_GTEST_VERSION} + GIT_SHALLOW TRUE + ) + FetchContent_MakeAvailable(googletest) + + if (NOT TARGET gtest::gtest) + add_library(gtest::gtest ALIAS gtest) + endif () +endfunction() + +function(ccmath_showcase_configure_target TARGET) + if (NOT TARGET ${TARGET}) + message(FATAL_ERROR "ccmath_showcase_configure_target: ${TARGET} does not exist") + endif () + target_compile_features(${TARGET} PRIVATE cxx_std_20) + target_include_directories(${TARGET} PRIVATE + ${CMAKE_SOURCE_DIR}/showcase/common + ${CMAKE_SOURCE_DIR}/benchmarks + ${CMAKE_SOURCE_DIR}/tests + ${CMAKE_SOURCE_DIR}/tests/shared/utils + ) + if (CCMATH_SHOWCASE_ENABLE_GCEM) + target_link_libraries(${TARGET} PRIVATE showcase::gcem) + target_compile_definitions(${TARGET} PRIVATE CCMATH_SHOWCASE_HAS_GCEM=1) + endif () +endfunction() diff --git a/cmake/config/TestCtestTargets.cmake b/cmake/config/TestCtestTargets.cmake new file mode 100644 index 00000000..deca0765 --- /dev/null +++ b/cmake/config/TestCtestTargets.cmake @@ -0,0 +1,57 @@ +# Convenience targets that run labeled ctest suites from the build tree root. + +include_guard(GLOBAL) + +if (NOT CCMATH_BUILD_TESTS) + return () +endif () + +include(ProcessorCount) +ProcessorCount(_ccmath_ctest_job_count) +if (_ccmath_ctest_job_count EQUAL 0) + set(_ccmath_ctest_job_count 1) +endif () + +function(_ccmath_add_ctest_label_target TARGET_NAME TEST_LABEL) + set(_ctest_args --output-on-failure -j ${_ccmath_ctest_job_count}) + + if (CMAKE_CONFIGURATION_TYPES) + list(APPEND _ctest_args -C $) + endif () + + if (TEST_LABEL) + list(APPEND _ctest_args -L ${TEST_LABEL}) + endif () + + if (TEST_LABEL) + set(_ctest_comment "Running ctest label ${TEST_LABEL}") + else () + set(_ctest_comment "Running all ctest tests") + endif () + + add_custom_target(${TARGET_NAME} + COMMAND ${CMAKE_CTEST_COMMAND} ${_ctest_args} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + USES_TERMINAL + COMMENT ${_ctest_comment} + ) +endfunction() + +if (CCMATH_BUILD_SIMPLE_TESTS) + _ccmath_add_ctest_label_target(ccmath-ctest-simple simple) +endif () + +if (CCMATH_BUILD_RIGOROUS_TESTS) + _ccmath_add_ctest_label_target(ccmath-ctest-rigorous rigorous) + _ccmath_add_ctest_label_target(ccmath-ctest-aspirational aspirational) + + if (CCMATH_ENABLE_MPFR_TESTS) + _ccmath_add_ctest_label_target(ccmath-ctest-rigorous-mpfr mpfr) + endif () + + if (CCMATH_ENABLE_COREMATH_TESTS) + _ccmath_add_ctest_label_target(ccmath-ctest-rigorous-coremath coremath) + endif () +endif () + +_ccmath_add_ctest_label_target(ccmath-ctest-all "") diff --git a/cmake/config/TestModuleRegistry.cmake b/cmake/config/TestModuleRegistry.cmake new file mode 100644 index 00000000..99b4abb7 --- /dev/null +++ b/cmake/config/TestModuleRegistry.cmake @@ -0,0 +1,26 @@ +# Declarative unit-test module registry. +# Add a new public math family: drop *_test.cpp under tests/src/math// and +# append MODULE TARGET pair below (LLVM-libc test/src layout). + +set(CCMATH_UNIT_MODULE_basic ccmath-simple-basic) +set(CCMATH_UNIT_MODULE_basic_EXCLUDE + src/math/basic/fma_constexpr_upward_config_test.cpp) +set(CCMATH_UNIT_MODULE_compare ccmath-simple-compare) +set(CCMATH_UNIT_MODULE_expo ccmath-simple-exponential) +set(CCMATH_UNIT_MODULE_fmanip ccmath-simple-fmanip) +set(CCMATH_UNIT_MODULE_nearest ccmath-simple-nearest) +set(CCMATH_UNIT_MODULE_power ccmath-simple-power) +set(CCMATH_UNIT_MODULE_trig ccmath-simple-trigonometric) +set(CCMATH_UNIT_MODULE_ext ccmath-simple-ext) +set(CCMATH_UNIT_MODULE_ext_EXCLUDE + src/ext/constexpr_smoke_test.cpp) + +set(CCMATH_UNIT_MATH_MODULE_ORDER + basic + compare + expo + fmanip + nearest + power + trig +) diff --git a/cmake/config/TestOracleTargets.cmake b/cmake/config/TestOracleTargets.cmake new file mode 100644 index 00000000..cd1b1063 --- /dev/null +++ b/cmake/config/TestOracleTargets.cmake @@ -0,0 +1,134 @@ +# Helper functions for rigorous MPFR and CORE-MATH oracle executables. + +include_guard(GLOBAL) + +function(ccmath_add_rigorous_oracle_target TARGET) + set(options) + set(oneValueArgs BACKEND) + set(multiValueArgs SOURCES LIBRARIES INCLUDE_DIRECTORIES) + cmake_parse_arguments(CCM_ORACLE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_ORACLE_BACKEND) + message(FATAL_ERROR "ccmath_add_rigorous_oracle_target requires BACKEND MPFR or COREMATH") + endif () + if (NOT CCM_ORACLE_SOURCES) + message(FATAL_ERROR "ccmath_add_rigorous_oracle_target requires at least one source file") + endif () + + add_executable(${TARGET}) + target_sources(${TARGET} PRIVATE ${CCM_ORACLE_SOURCES}) + target_link_libraries(${TARGET} PRIVATE ccmath::ccmath ccmath::ext) + + if (CCM_ORACLE_BACKEND STREQUAL "MPFR") + if (NOT TARGET ccmath::mpfr_oracle) + message(FATAL_ERROR "ccmath_add_rigorous_oracle_target(${TARGET}): ccmath::mpfr_oracle target is unavailable") + endif () + target_link_libraries(${TARGET} PRIVATE ccmath::mpfr_oracle) + elseif (CCM_ORACLE_BACKEND STREQUAL "COREMATH") + if (NOT TARGET ccmath::coremath_oracle) + message(FATAL_ERROR "ccmath_add_rigorous_oracle_target(${TARGET}): ccmath::coremath_oracle target is unavailable") + endif () + target_link_libraries(${TARGET} PRIVATE ccmath::coremath_oracle) + if (NOT WIN32) + target_link_libraries(${TARGET} PRIVATE m) + endif () + else () + message(FATAL_ERROR "ccmath_add_rigorous_oracle_target(${TARGET}): unknown BACKEND ${CCM_ORACLE_BACKEND}") + endif () + + if (CCM_ORACLE_LIBRARIES) + target_link_libraries(${TARGET} PRIVATE ${CCM_ORACLE_LIBRARIES}) + endif () + + target_include_directories(${TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../shared + ${CMAKE_CURRENT_SOURCE_DIR}/.. + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CCM_ORACLE_INCLUDE_DIRECTORIES} + ) + target_compile_features(${TARGET} PRIVATE cxx_std_17) + ccmath_apply_pow_validation_options(${TARGET}) +endfunction() + +function(ccmath_add_rigorous_oracle_test TEST_NAME TARGET) + set(options) + set(oneValueArgs TIMEOUT WORKING_DIRECTORY) + set(multiValueArgs LABELS ARGS) + cmake_parse_arguments(CCM_ORACLE_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT TARGET ${TARGET}) + message(FATAL_ERROR "ccmath_add_rigorous_oracle_test(${TEST_NAME}): target ${TARGET} does not exist") + endif () + + set(_command ${TARGET}) + if (CCM_ORACLE_TEST_ARGS) + list(APPEND _command ${CCM_ORACLE_TEST_ARGS}) + endif () + + add_test(NAME ${TEST_NAME} COMMAND ${_command}) + + if (CCM_ORACLE_TEST_LABELS) + set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${CCM_ORACLE_TEST_LABELS}") + endif () + if (CCM_ORACLE_TEST_TIMEOUT) + set_tests_properties(${TEST_NAME} PROPERTIES TIMEOUT ${CCM_ORACLE_TEST_TIMEOUT}) + endif () + if (CCM_ORACLE_TEST_WORKING_DIRECTORY) + set_tests_properties(${TEST_NAME} PROPERTIES WORKING_DIRECTORY "${CCM_ORACLE_TEST_WORKING_DIRECTORY}") + endif () +endfunction() + +function(ccmath_ensure_mpfr_oracle_target TARGET) + set(options) + set(oneValueArgs) + set(multiValueArgs SOURCES) + cmake_parse_arguments(CCM_ENSURE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (TARGET ${TARGET}) + return() + endif () + if (NOT CCM_ENSURE_SOURCES) + message(FATAL_ERROR "ccmath_ensure_mpfr_oracle_target(${TARGET}): SOURCES is required") + endif () + ccmath_add_rigorous_oracle_target(${TARGET} BACKEND MPFR SOURCES ${CCM_ENSURE_SOURCES}) +endfunction() + +function(ccmath_ensure_coremath_oracle_target TARGET) + set(options) + set(oneValueArgs) + set(multiValueArgs SOURCES) + cmake_parse_arguments(CCM_ENSURE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (TARGET ${TARGET}) + return() + endif () + if (NOT CCM_ENSURE_SOURCES) + message(FATAL_ERROR "ccmath_ensure_coremath_oracle_target(${TARGET}): SOURCES is required") + endif () + ccmath_add_rigorous_oracle_target(${TARGET} BACKEND COREMATH SOURCES ${CCM_ENSURE_SOURCES}) +endfunction() + +function(ccmath_add_oracle_ctest TEST_NAME) + set(options) + set(oneValueArgs TARGET TIMEOUT WORKING_DIRECTORY LOG_OUTPUT JSON_OUTPUT) + set(multiValueArgs LABELS ARGS) + cmake_parse_arguments(CCM_CTEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_CTEST_TARGET) + message(FATAL_ERROR "ccmath_add_oracle_ctest(${TEST_NAME}): TARGET is required") + endif () + + set(_command_args ${CCM_CTEST_ARGS}) + if (CCM_CTEST_LOG_OUTPUT) + list(APPEND _command_args --log-output=${CCM_CTEST_LOG_OUTPUT}) + endif () + if (CCM_CTEST_JSON_OUTPUT) + list(APPEND _command_args --json-output=${CCM_CTEST_JSON_OUTPUT}) + endif () + + ccmath_add_rigorous_oracle_test(${TEST_NAME} ${CCM_CTEST_TARGET} + LABELS ${CCM_CTEST_LABELS} + TIMEOUT ${CCM_CTEST_TIMEOUT} + ARGS ${_command_args} + WORKING_DIRECTORY ${CCM_CTEST_WORKING_DIRECTORY}) +endfunction() diff --git a/cmake/config/TestValidationOptions.cmake b/cmake/config/TestValidationOptions.cmake new file mode 100644 index 00000000..51bab85d --- /dev/null +++ b/cmake/config/TestValidationOptions.cmake @@ -0,0 +1,55 @@ +# Apply pow validation compile options to test and oracle targets. + +function(ccmath_get_pow_validation_configuration_name OUT_VAR) + set(_parts "") + if (CCMATH_TEST_DISABLE_RUNTIME_BUILTIN) + list(APPEND _parts "no-builtin") + endif () + if (CCMATH_TEST_DISABLE_FMA) + list(APPEND _parts "no-fma") + elseif (CCMATH_TEST_FORCE_FMA) + list(APPEND _parts "force-fma") + endif () + if (CCMATH_DISABLE_RUNTIME_SIMD) + list(APPEND _parts "no-simd") + endif () + if (CCMATH_DISABLE_REDUCED_PRECISION_POWL) + list(APPEND _parts "powl-strict") + else () + list(APPEND _parts "powl-fallback") + endif () + if (NOT _parts) + set(_parts "default") + endif () + string(REPLACE ";" "-" _name "${_parts}") + set(${OUT_VAR} "${_name}" PARENT_SCOPE) +endfunction() + +function(ccmath_apply_pow_validation_options TARGET) + if (NOT TARGET ${TARGET}) + message(FATAL_ERROR "ccmath_apply_pow_validation_options: target ${TARGET} does not exist") + endif () + + if (CCMATH_TEST_DISABLE_RUNTIME_BUILTIN) + target_compile_definitions(${TARGET} PRIVATE CCM_CONFIG_TEST_DISABLE_RUNTIME_BUILTIN_POW) + endif () + + if (CCMATH_TEST_DISABLE_FMA) + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang|IntelLLVM") + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|i[3-6]86)$") + target_compile_options(${TARGET} PRIVATE -fno-fma) + endif () + endif () + endif () + + if (CCMATH_TEST_FORCE_FMA) + if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|i[3-6]86)$") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang|IntelLLVM") + target_compile_options(${TARGET} PRIVATE -mfma) + endif () + endif () + endif () + + ccmath_get_pow_validation_configuration_name(_config_name) + target_compile_definitions(${TARGET} PRIVATE CCM_CONFIG_TEST_POW_VALIDATION_NAME="${_config_name}") +endfunction() diff --git a/cmake/config/UserOptions.cmake b/cmake/config/UserOptions.cmake index 625f1639..f3f35a54 100644 --- a/cmake/config/UserOptions.cmake +++ b/cmake/config/UserOptions.cmake @@ -12,10 +12,66 @@ option(CCMATH_BUILD_EXAMPLES "Build ccmath examples" ${CCMATH_PROJECT_IS_TOP_LEV # Enable building of ccmath tests. If OFF, tests are skipped. Should only be used by ccmath developers. option(CCMATH_BUILD_TESTS "Build ccmath tests" ${CCMATH_PROJECT_IS_TOP_LEVEL}) +# CCMATH_BUILD_SIMPLE_TESTS: +# Build the lightweight unit/regression suite intended for every developer and CI run. +option(CCMATH_BUILD_SIMPLE_TESTS "Build the lightweight ccmath test project" ${CCMATH_PROJECT_IS_TOP_LEVEL}) + +# CCMATH_BUILD_RIGOROUS_TESTS: +# Build the heavier validation/oracle suite intended for post-merge, nightly, or release validation. +option(CCMATH_BUILD_RIGOROUS_TESTS "Build the rigorous ccmath validation project" OFF) + +# CCMATH_ENABLE_MPFR_TESTS: +# Enable MPFR-backed oracle validation binaries when MPFR and GMP are available. +option(CCMATH_ENABLE_MPFR_TESTS "Enable MPFR-backed rigorous oracle executables" OFF) + +# CCMATH_ENABLE_COREMATH_TESTS: +# Enable CORE-MATH-backed oracle validation binaries when CORE-MATH is discoverable. +option(CCMATH_ENABLE_COREMATH_TESTS "Enable CORE-MATH-backed rigorous oracle executables" OFF) + +# CCMATH_COREMATH_ROOT: +# Optional install prefix for a pre-built CORE-MATH oracle library (include + lib). +set(CCMATH_COREMATH_ROOT "" CACHE PATH "Install prefix for CORE-MATH oracle library") + +# CCMATH_FETCH_COREMATH: +# Last-resort FetchContent build when prefix/pkg-config discovery fails. +option(CCMATH_FETCH_COREMATH "Fetch and build CORE-MATH sources when not found on the system" OFF) + +# CCMATH_ENABLE_SEARCH_TOOLS: +# Build standalone adversarial-search tools (worst-case input hunting). +option(CCMATH_ENABLE_SEARCH_TOOLS "Build standalone adversarial-search tools (worst-case input hunting)" OFF) + +# CCMATH_ENABLE_CROSS_LIBM_TESTS: +# Build optional cross-libm differential pow validation tools. +option(CCMATH_ENABLE_CROSS_LIBM_TESTS "Build optional cross-libm pow differential tools" OFF) + +# CCMATH_TEST_DISABLE_RUNTIME_BUILTIN: +# Disable runtime __builtin_pow dispatch in pow_rt for validation builds. +option(CCMATH_TEST_DISABLE_RUNTIME_BUILTIN "Disable runtime builtin pow in validation builds" OFF) + +# CCMATH_TEST_DISABLE_FMA: +# Force non-FMA generic pow kernel branches in validation builds. +option(CCMATH_TEST_DISABLE_FMA "Disable FMA pow kernel branches in validation builds" OFF) + +# CCMATH_TEST_FORCE_FMA: +# Request FMA-capable compilation for validation builds when supported. +option(CCMATH_TEST_FORCE_FMA "Force FMA-capable compilation for validation builds" OFF) + # CCMATH_BUILD_BENCHMARKS: # Enable building of ccmath benchmarks. If OFF, benchmarks are skipped. option(CCMATH_BUILD_BENCHMARKS "Build ccmath benchmarks" OFF) +# CCMATH_BUILD_SHOWCASE: +# Build the external library comparison showcase (GCEM first, MPFR accuracy, gbench perf). +option(CCMATH_BUILD_SHOWCASE "Build ccmath showcase comparisons" OFF) + +# CCMATH_SHOWCASE_ENABLE_GCEM: +# Fetch and enable the GCEM backend in showcase targets. +option(CCMATH_SHOWCASE_ENABLE_GCEM "Enable GCEM in showcase comparisons" ${CCMATH_PROJECT_IS_TOP_LEVEL}) + +# CCMATH_SHOWCASE_ENABLE_MPFR: +# Build MPFR-backed showcase accuracy executables when MPFR is available. +option(CCMATH_SHOWCASE_ENABLE_MPFR "Enable MPFR accuracy in showcase comparisons" ${CCMATH_PROJECT_IS_TOP_LEVEL}) + # CCMATH_BUILD_FUZZING: # Build libFuzzer targets (Clang only). Couples fuzzer with ASan and UBSan. option(CCMATH_BUILD_FUZZING "Build libFuzzer fuzz targets (Clang only)" OFF) @@ -34,17 +90,8 @@ option(CCMATH_ENABLE_EXTENSIONS "Enable the extended ccmath library that adds additional methods beyond the standard" ${CCMATH_PROJECT_IS_TOP_LEVEL}) -# CCMATH_DISABLE_ERRNO: -# Disable usage of errno during runtime breaking standard compliance, but may lead to faster evaluation and smaller binaries. -option(CCMATH_DISABLE_ERRNO - "Disable the use of errno in ccmath during runtime (may lead to faster evaluations but is non-standard)" - OFF) - -# CCMATH_ENABLE_RUNTIME_SIMD: -# Enable runtime SIMD optimizations for faster evaluations at runtime. -option(CCMATH_DISABLE_RUNTIME_SIMD - "Enable SIMD optimization for runtime evaluation (does not affect compile-time)" - OFF) +include(cmake/config/BuildManifest.cmake) +ccmath_manifest_declare_library_options() # CCMATH_DISABLE_SVML_USAGE: # Disable the use of SVML (Short Vector Math Library) if supported by the compiler. @@ -52,6 +99,16 @@ option(CCMATH_DISABLE_SVML_USAGE "Disable the use of SVML functions in ccmath (if supported by compiler)" OFF) +# CCMATH_SIMD_PREFER: +# Opt-in: choose which SIMD instruction set ccmath builds compile for. +# DEFAULT - use whatever the toolchain / -march already selects (no extra flag). +# LOWEST - the most portable baseline for the architecture (SSE2 on x86_64, NEON on AArch64). +# HIGHEST - the best instruction set in ccmath's supported ladder the compiler can emit (up to AVX2, AVX-512 excluded). +# Defaults to DEFAULT (does nothing). Downstream consumers may enable or disable it. +set(CCMATH_SIMD_PREFER "DEFAULT" CACHE STRING + "Preferred SIMD instruction set for ccmath builds: DEFAULT, LOWEST or HIGHEST") +set_property(CACHE CCMATH_SIMD_PREFER PROPERTY STRINGS DEFAULT LOWEST HIGHEST) + # CCMATH_DISABLE_CMAKE_FEATURE_CHECKS: # Disable cmakes ability to check for certain features at the CMake level. option(CCMATH_DISABLE_CMAKE_FEATURE_CHECKS diff --git a/cmake/coremath/CMakeLists.txt b/cmake/coremath/CMakeLists.txt new file mode 100644 index 00000000..6d4ce618 --- /dev/null +++ b/cmake/coremath/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.18) +project(ccmath_coremath_oracle LANGUAGES C) + +if (NOT CORE_MATH_SOURCE_DIR) + message(FATAL_ERROR "CORE_MATH_SOURCE_DIR must point at a CORE-MATH source tree") +endif () + +add_library(coremath_oracle STATIC + ${CORE_MATH_SOURCE_DIR}/src/binary64/pow/pow.c + ${CORE_MATH_SOURCE_DIR}/src/binary32/pow/powf.c +) + +target_include_directories(coremath_oracle + PUBLIC + $ + $ + PRIVATE + ${CORE_MATH_SOURCE_DIR}/src/binary64/pow +) + +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$") + target_compile_options(coremath_oracle PRIVATE -march=x86-64-v3) +endif () + +if (MSVC) + target_compile_options(coremath_oracle PRIVATE -O2 -fp:strict) +else () + target_compile_options(coremath_oracle PRIVATE -O2 -fno-fast-math -ffp-contract=off) +endif () + +include(GNUInstallDirs) + +install(TARGETS coremath_oracle + EXPORT ccmath_coremath_oracle_targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/crmath.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +install(EXPORT ccmath_coremath_oracle_targets + FILE ccmath_coremath_oracle_targets.cmake + NAMESPACE ccmath:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ccmath_coremath_oracle +) diff --git a/cmake/coremath/crmath.h b/cmake/coremath/crmath.h new file mode 100644 index 00000000..21ef5498 --- /dev/null +++ b/cmake/coremath/crmath.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + + float cr_powf(float x, float y); + double cr_pow(double x, double y); + +#ifdef __cplusplus +} +#endif diff --git a/cmake/features/CheckAllSupportedSimdFeatures.cmake b/cmake/features/CheckAllSupportedSimdFeatures.cmake index dc810ac3..a814ab4e 100644 --- a/cmake/features/CheckAllSupportedSimdFeatures.cmake +++ b/cmake/features/CheckAllSupportedSimdFeatures.cmake @@ -2,6 +2,10 @@ if (NOT DEFINED CCMATH_ROOT_DIR) message(FATAL_ERROR "CCMATH_ROOT_DIR is not defined. Did you forget to include the main CMakeLists.txt?") endif () +# Select the preferred instruction set first so the FMA / SVML probes below are +# evaluated at the chosen level (via CMAKE_REQUIRED_FLAGS). +include(${CCMATH_ROOT_DIR}/cmake/features/simd/SelectSimdInstructionSet.cmake) + include(${CCMATH_ROOT_DIR}/cmake/features/simd/CheckFMASupport.cmake) if (NOT CCMATH_DISABLE_SVML_USAGE) diff --git a/cmake/features/simd/SelectSimdInstructionSet.cmake b/cmake/features/simd/SelectSimdInstructionSet.cmake new file mode 100644 index 00000000..40e8c441 --- /dev/null +++ b/cmake/features/simd/SelectSimdInstructionSet.cmake @@ -0,0 +1,133 @@ +# Select and apply a preferred SIMD instruction set for ccmath's in-tree build. +# +# Controlled by CCMATH_SIMD_PREFER (DEFAULT | LOWEST | HIGHEST), declared in +# cmake/config/UserOptions.cmake: +# DEFAULT leave whatever the toolchain / -march already selects (no extra flag). +# LOWEST target the most portable baseline for the architecture +# (SSE2 on x86_64, NEON on AArch64). +# HIGHEST target the best instruction set in ccmath's supported ladder that the +# compiler can emit (up to AVX2). AVX-512 is intentionally excluded. +# +# This is opt-in: the default does nothing. When enabled, the chosen flag is +# attached to the ccmath interface target behind a BUILD_INTERFACE generator +# expression, so it affects this build tree (library, tests, examples, +# benchmarks and in-tree consumers that opt in) without leaking into the +# installed / exported package. CMAKE_REQUIRED_FLAGS is also updated so the later +# FMA / SVML feature probes are evaluated at the selected level. + +if (NOT DEFINED CCMATH_SIMD_PREFER OR CCMATH_SIMD_PREFER STREQUAL "DEFAULT") + return() +endif () + +if (NOT CCMATH_SIMD_PREFER STREQUAL "LOWEST" AND NOT CCMATH_SIMD_PREFER STREQUAL "HIGHEST") + message(WARNING "CCMath: unknown CCMATH_SIMD_PREFER='${CCMATH_SIMD_PREFER}' (expected DEFAULT, LOWEST or HIGHEST). Ignoring.") + return() +endif () + +if (NOT DEFINED CCMATH_TARGET_NAME) + set(CCMATH_TARGET_NAME ccmath) +endif () + +include(CheckCXXSourceCompiles) + +# Probe whether the compiler accepts ${_flag} and can compile ${_src}. A flag of +# NONE means "no extra flag" (used for baseline rungs, an empty string cannot be +# carried as a list element). +function(_ccmath_probe_isa _flag _src _resultvar) + set(_saved "${CMAKE_REQUIRED_FLAGS}") + if (_flag AND NOT _flag STREQUAL "NONE") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${_flag}") + endif () + check_cxx_source_compiles("${_src}" ${_resultvar}) + set(CMAKE_REQUIRED_FLAGS "${_saved}") + set(${_resultvar} "${${_resultvar}}" PARENT_SCOPE) +endfunction() + +# Representative intrinsic per rung. +set(_src_sse2 [=[ +#include +int main() { __m128d v = _mm_set1_pd(1.0); v = _mm_add_pd(v, v); return (int) _mm_cvtsd_f64(v); } +]=]) +set(_src_sse42 [=[ +#include +int main() { __m128 v = _mm_set1_ps(1.0f); v = _mm_dp_ps(v, v, 0xFF); return (int) _mm_cvtss_f32(v); } +]=]) +set(_src_avx [=[ +#include +int main() { __m256 v = _mm256_set1_ps(1.0f); v = _mm256_add_ps(v, v); return (int) _mm256_cvtss_f32(v); } +]=]) +set(_src_avx2 [=[ +#include +int main() { __m256i v = _mm256_set1_epi32(1); v = _mm256_add_epi32(v, v); return _mm256_extract_epi32(v, 0); } +]=]) +set(_src_neon [=[ +#include +int main() { float32x4_t v = vdupq_n_f32(1.0f); v = vaddq_f32(v, v); return (int) vgetq_lane_f32(v, 0); } +]=]) + +# Build the rung ladder (low -> high) for this architecture and compiler. Only +# the rung names and flags are kept as lists. The source snippets contain +# semicolons and must never be stored as list elements (they are looked up by +# name as _src_ instead). +set(_names "") +set(_flags "") + +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64|i[3-6]86|x86)$") + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # MSVC: SSE2 is the x64 baseline (no flag), and no SSE4 arch switch exists. + set(_names SSE2 AVX AVX2) + set(_flags NONE "/arch:AVX" "/arch:AVX2") + else () + # Use the x86-64 microarchitecture levels (matches cmake/coremath): v1 is + # the SSE2 baseline, v2 ~ SSE4.2, v3 ~ AVX2+FMA. v4 (AVX-512) is excluded. + # A march level also caps the ISA, overriding any higher -m flag a preset + # may have set earlier on the command line. + set(_names SSE2 SSE4.2 AVX2) + set(_flags "-march=x86-64" "-march=x86-64-v2" "-march=x86-64-v3") + endif () +elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$") + # NEON is the mandatory AArch64 baseline. SVE is intentionally out of scope. + set(_names NEON) + set(_flags NONE) +else () + message(STATUS "CCMath: CCMATH_SIMD_PREFER=${CCMATH_SIMD_PREFER} has no instruction-set ladder for '${CMAKE_SYSTEM_PROCESSOR}'; leaving toolchain default.") + return() +endif () + +list(LENGTH _names _count) +math(EXPR _last "${_count} - 1") + +# LOWEST: first rung that compiles. HIGHEST: highest rung that compiles. +set(_chosen -1) +foreach (_i RANGE 0 ${_last}) + list(GET _names ${_i} _nm) + list(GET _flags ${_i} _fl) + # Look up the matching source snippet by name (e.g. SSE4.2 -> _src_sse42). + string(TOLOWER "${_nm}" _lc) + string(REPLACE "." "" _lc "${_lc}") + set(_sr "${_src_${_lc}}") + string(MAKE_C_IDENTIFIER "CCMATH_PROBE_ISA_${_nm}" _rv) + _ccmath_probe_isa("${_fl}" "${_sr}" ${_rv}) + if (${_rv}) + set(_chosen ${_i}) + if (CCMATH_SIMD_PREFER STREQUAL "LOWEST") + break() + endif () + endif () +endforeach () + +if (_chosen LESS 0) + message(WARNING "CCMath: could not select a SIMD instruction set for CCMATH_SIMD_PREFER=${CCMATH_SIMD_PREFER}; leaving toolchain default.") + return() +endif () + +list(GET _names ${_chosen} _chosen_name) +list(GET _flags ${_chosen} _chosen_flag) + +if (_chosen_flag AND NOT _chosen_flag STREQUAL "NONE") + target_compile_options(${CCMATH_TARGET_NAME} INTERFACE "$") + string(APPEND CMAKE_REQUIRED_FLAGS " ${_chosen_flag}") + message(STATUS "CCMath: SIMD instruction set (${CCMATH_SIMD_PREFER}) -> ${_chosen_name} (${_chosen_flag})") +else () + message(STATUS "CCMath: SIMD instruction set (${CCMATH_SIMD_PREFER}) -> ${_chosen_name} (toolchain baseline)") +endif () diff --git a/cmake/gen/ccmath/meson.build b/cmake/gen/ccmath/meson.build new file mode 100644 index 00000000..ed947d2e --- /dev/null +++ b/cmake/gen/ccmath/meson.build @@ -0,0 +1,15 @@ +# Generated by tools/gen_secondary_builds.py. Do not edit by hand. +# Produces /cmake/gen/ccmath/version.hpp from the version template, so the +# 'ccmath' include prefix resolves without a CMake configure or a checked-in header. + +_ccmath_version_parts = meson.project_version().split('.') +_ccmath_version_conf = configuration_data() +_ccmath_version_conf.set('CCMATH_VERSION_MAJOR', _ccmath_version_parts[0]) +_ccmath_version_conf.set('CCMATH_VERSION_MINOR', _ccmath_version_parts[1]) +_ccmath_version_conf.set('CCMATH_VERSION_PATCH', _ccmath_version_parts[2]) + +ccmath_version_hpp = configure_file( + input: meson.project_source_root() / 'cmake/in/version.hpp.in', + output: 'version.hpp', + configuration: _ccmath_version_conf, +) diff --git a/cmake/gen/meson.build b/cmake/gen/meson.build new file mode 100644 index 00000000..30e907d9 --- /dev/null +++ b/cmake/gen/meson.build @@ -0,0 +1,81 @@ +# Generated by tools/gen_secondary_builds.py. Do not edit by hand. +# Exposes CCMath as a Meson dependency only. + +cpp = meson.get_compiler('cpp') + +_ccmath_builtin_bit_cast_probe = ''' +#include +int main() { + struct A { int x; }; + struct B { int y; }; + B b = __builtin_bit_cast(B, A{42}); + return b.y; +} +''' + +if not cpp.compiles(_ccmath_builtin_bit_cast_probe, name : 'ccmath builtin bit_cast support') + error('CCMath requires compiler support for __builtin_bit_cast') +endif + +_ccmath_defines = [] +if get_option('enable_runtime_simd') and not get_option('deterministic') + _ccmath_defines += '-DCCM_CONFIG_USE_RT_SIMD' +endif +if get_option('disable_errno') + _ccmath_defines += '-DCCM_CONFIG_DISABLE_ERRNO' +endif +if get_option('disable_fenv') + _ccmath_defines += '-DCCM_CONFIG_DISABLE_FENV' +endif +if get_option('freestanding') + _ccmath_defines += '-DCCM_CONFIG_FREESTANDING' +endif +if get_option('disable_reduced_precision_powl') + _ccmath_defines += '-DCCM_CONFIG_DISABLE_REDUCED_PRECISION_POWL' +endif +if get_option('deterministic') + _ccmath_defines += '-DCCM_CONFIG_DETERMINISTIC' +endif +if get_option('deterministic') + _ccmath_defines += '-ffp-contract=off' +endif + +_ccmath_simd_fma_probe = ''' +#include +int main() { + __m128 fma_test = _mm_fmadd_ps(_mm_set1_ps(1.0f), _mm_set1_ps(2.0f), _mm_set1_ps(3.0f)); + return static_cast(_mm_cvtss_f32(fma_test)); +} +''' + +if get_option('enable_runtime_simd') and not get_option('deterministic') and cpp.compiles(_ccmath_simd_fma_probe, name : 'ccmath runtime SIMD FMA intrinsics') + _ccmath_defines += '-DCCM_CONFIG_RT_SIMD_HAS_FMA' +endif + +_ccmath_simd_svml_probe = ''' +#include +int main() { + __m128d value = _mm_sin_pd(_mm_set1_pd(1.0)); + return static_cast(_mm_cvtsd_f64(value)); +} +''' + +if get_option('enable_runtime_simd') and not get_option('deterministic') and cpp.compiles(_ccmath_simd_svml_probe, name : 'ccmath runtime SIMD SVML intrinsics') + _ccmath_defines += '-DCCM_CONFIG_RT_SIMD_HAS_SVML' +endif + +# Generate ccmath/version.hpp natively so Meson consumers never depend on a CMake configure. +subdir('ccmath') + +ccmath_inc = include_directories( + '../../include', + '.', +) + +ccmath_dep = declare_dependency( + include_directories: ccmath_inc, + compile_args: _ccmath_defines, + sources: ccmath_version_hpp, +) + +meson.override_dependency('ccmath', ccmath_dep) diff --git a/cmake/helpers/CcmBenchHelpers.cmake b/cmake/helpers/CcmBenchHelpers.cmake new file mode 100644 index 00000000..b0252b3f --- /dev/null +++ b/cmake/helpers/CcmBenchHelpers.cmake @@ -0,0 +1,86 @@ +# Google Benchmark target registration (LLVM-libc style: shared harness, per-function drivers). + +include_guard(GLOBAL) + +if (NOT DEFINED CCMATH_BENCH_ROOT) + message(FATAL_ERROR "CCMATH_BENCH_ROOT must be set before including CcmBenchHelpers.cmake") +endif () + +function(ccmath_bench_module_enabled MODULE OUT_VAR) + set(_option_var CCMATH_BENCH_MODULE_${MODULE}_OPTION) + if (NOT DEFINED ${_option_var}) + message(FATAL_ERROR "ccmath_bench_module_enabled(${MODULE}): missing ${_option_var}") + endif () + + if (CCM_BENCH_ALL) + set(${OUT_VAR} TRUE PARENT_SCOPE) + return () + endif () + + if (${${_option_var}}) + set(${OUT_VAR} TRUE PARENT_SCOPE) + else () + set(${OUT_VAR} FALSE PARENT_SCOPE) + endif () +endfunction() + +function(ccmath_add_bench_target FUNCTION) + set(options) + set(oneValueArgs MODULE) + set(multiValueArgs SOURCES COMPILE_DEFINITIONS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (CCM_MODULE AND NOT CCM_SOURCES) + set(CCM_SOURCES "${CCMATH_BENCH_ROOT}/src/math/${CCM_MODULE}/${FUNCTION}.bench.cpp") + endif () + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_bench_target(${FUNCTION}): SOURCES or MODULE is required") + endif () + + set(_target ccm_benchmark_${FUNCTION}) + add_executable(${_target} ${CCM_SOURCES}) + target_link_libraries(${_target} PRIVATE ccmath::ccmath benchmark::benchmark) + target_include_directories(${_target} PRIVATE "${CCMATH_BENCH_ROOT}/shared" "${CCMATH_BENCH_ROOT}") + target_compile_features(${_target} PRIVATE cxx_std_17) + + if (CCM_COMPILE_DEFINITIONS) + target_compile_definitions(${_target} PRIVATE ${CCM_COMPILE_DEFINITIONS}) + endif () + + set(${_target}_REGISTERED TRUE PARENT_SCOPE) +endfunction() + +function(ccmath_apply_bench_registry) + if (NOT TARGET benchmark::benchmark) + message(FATAL_ERROR "ccmath_apply_bench_registry requires benchmark::benchmark") + endif () + + set(_all_targets "") + foreach (_module IN LISTS CCMATH_BENCH_MODULE_ORDER) + ccmath_bench_module_enabled(${_module} _enabled) + if (NOT _enabled) + continue () + endif () + + set(_functions_var CCMATH_BENCH_MODULE_${_module}_FUNCTIONS) + foreach (_function IN LISTS ${_functions_var}) + ccmath_add_bench_target(${_function} MODULE ${_module}) + list(APPEND _all_targets ccm_benchmark_${_function}) + endforeach () + + if (_module STREQUAL "power") + if (EXISTS "${CMAKE_SOURCE_DIR}/tools/asmlab/bench/CMakeLists.txt") + add_subdirectory("${CMAKE_SOURCE_DIR}/tools/asmlab/bench" "${CMAKE_BINARY_DIR}/asmlab_bench") + endif () + if (EXISTS "${CMAKE_SOURCE_DIR}/tools/asmlab/accuracy/CMakeLists.txt") + add_subdirectory("${CMAKE_SOURCE_DIR}/tools/asmlab/accuracy" "${CMAKE_BINARY_DIR}/asmlab_accuracy") + endif () + endif () + endforeach () + + if (_all_targets) + add_custom_target(ccm_benchmark_all) + add_dependencies(ccm_benchmark_all ${_all_targets}) + endif () +endfunction() diff --git a/cmake/helpers/CcmExampleHelpers.cmake b/cmake/helpers/CcmExampleHelpers.cmake new file mode 100644 index 00000000..a2b9a9a7 --- /dev/null +++ b/cmake/helpers/CcmExampleHelpers.cmake @@ -0,0 +1,56 @@ +# Example executable registration (showcase-style layout, registry-driven). + +include_guard(GLOBAL) + +if (NOT DEFINED CCMATH_EXAMPLE_ROOT) + message(FATAL_ERROR "CCMATH_EXAMPLE_ROOT must be set before including CcmExampleHelpers.cmake") +endif () + +function(ccmath_add_example_target ID) + set(options) + set(oneValueArgs MODULE CXX_STANDARD) + set(multiValueArgs SOURCES COMPILE_DEFINITIONS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (CCM_MODULE AND NOT CCM_SOURCES) + set(CCM_SOURCES "${CCMATH_EXAMPLE_ROOT}/src/${ID}.cpp") + endif () + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_example_target(${ID}): SOURCES or MODULE is required") + endif () + + set(_target ccmath-example-${ID}) + add_executable(${_target} ${CCM_SOURCES}) + target_link_libraries(${_target} PRIVATE ccmath::ccmath) + target_include_directories(${_target} PRIVATE "${CCMATH_EXAMPLE_ROOT}/common" "${CCMATH_EXAMPLE_ROOT}") + set_property(TARGET ${_target} PROPERTY FOLDER "examples") + if (CCM_CXX_STANDARD) + target_compile_features(${_target} PRIVATE cxx_std_${CCM_CXX_STANDARD}) + else () + set_property(TARGET ${_target} PROPERTY CXX_STANDARD 17) + endif () + + if (CCM_COMPILE_DEFINITIONS) + target_compile_definitions(${_target} PRIVATE ${CCM_COMPILE_DEFINITIONS}) + endif () + + set(${_target}_REGISTERED TRUE PARENT_SCOPE) +endfunction() + +function(ccmath_apply_example_registry) + set(_all_targets "") + foreach (_module IN LISTS CCMATH_EXAMPLE_MODULE_ORDER) + set(_examples_var CCMATH_EXAMPLE_MODULE_${_module}_EXAMPLES) + foreach (_example IN LISTS ${_examples_var}) + ccmath_add_example_target(${_example} MODULE ${_module}) + list(APPEND _all_targets ccmath-example-${_example}) + endforeach () + endforeach () + + if (_all_targets) + add_custom_target(ccmath-example-all) + add_dependencies(ccmath-example-all ${_all_targets}) + set_property(TARGET ccmath-example-all PROPERTY FOLDER "examples") + endif () +endfunction() diff --git a/cmake/helpers/CcmFuzzHelpers.cmake b/cmake/helpers/CcmFuzzHelpers.cmake new file mode 100644 index 00000000..e10480f5 --- /dev/null +++ b/cmake/helpers/CcmFuzzHelpers.cmake @@ -0,0 +1,129 @@ +# libFuzzer target registration (LLVM-libc style: shared harness, per-module drivers). +# +# Requires CCMATH_FUZZ_ROOT and CCMATH_FUZZ_COMPILE_FLAGS / CCMATH_FUZZ_LINK_FLAGS +# from ccmath_detect_libfuzzer() before include. + +include_guard(GLOBAL) + +if (NOT DEFINED CCMATH_FUZZ_ROOT) + message(FATAL_ERROR "CCMATH_FUZZ_ROOT must be set before including CcmFuzzHelpers.cmake") +endif () + +set(CCMATH_FUZZ_SHARED_SEED_DIR "${CCMATH_FUZZ_ROOT}/seeds/common") +set(CCMATH_FUZZ_DICT "${CCMATH_FUZZ_ROOT}/dicts/ccmath.dict") + +function(ccmath_fuzz_default_source MODULE OUT_VAR) + if (MODULE STREQUAL "ext") + set(_source "${CCMATH_FUZZ_ROOT}/src/ext/${MODULE}_fuzz.cpp") + elseif (MODULE MATCHES "^internal/") + set(_source "${CCMATH_FUZZ_ROOT}/src/math/${MODULE}/${MODULE}_fuzz.cpp") + else () + set(_source "${CCMATH_FUZZ_ROOT}/src/math/${MODULE}_fuzz.cpp") + endif () + set(${OUT_VAR} ${_source} PARENT_SCOPE) +endfunction() + +function(ccmath_ensure_fuzz_seeds OUT_DIR) + if (NOT EXISTS "${OUT_DIR}/zero.bin") + file(MAKE_DIRECTORY "${OUT_DIR}") + find_program(_ccmath_fuzz_python3 NAMES python3 python REQUIRED) + execute_process( + COMMAND "${_ccmath_fuzz_python3}" + "${CCMATH_FUZZ_ROOT}/scripts/generate_seeds.py" + "${OUT_DIR}" + RESULT_VARIABLE _seed_result) + if (NOT _seed_result EQUAL 0) + message(FATAL_ERROR "Failed to generate fuzz seeds in ${OUT_DIR}") + endif () + endif () +endfunction() + +function(ccmath_add_fuzz_target TARGET) + set(options) + set(oneValueArgs MODULE) + set(multiValueArgs SOURCES) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (CCM_MODULE AND NOT CCM_SOURCES) + ccmath_fuzz_default_source(${CCM_MODULE} CCM_SOURCES) + endif () + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_fuzz_target(${TARGET}): SOURCES or MODULE is required") + endif () + + add_executable(${TARGET} ${CCM_SOURCES}) + target_link_libraries(${TARGET} PRIVATE ccmath::ccmath) + target_include_directories(${TARGET} PRIVATE "${CCMATH_FUZZ_ROOT}/shared") + target_compile_features(${TARGET} PRIVATE cxx_std_17) + target_compile_options(${TARGET} PRIVATE ${CCMATH_FUZZ_COMPILE_FLAGS} -Wno-everything) + if (CCMATH_FUZZ_GENERIC) + target_compile_definitions(${TARGET} PRIVATE CCMATH_FUZZ_GENERIC) + endif () + target_link_options(${TARGET} PRIVATE ${CCMATH_FUZZ_LINK_FLAGS}) + + set(_corpus_dir "${CMAKE_CURRENT_BINARY_DIR}/corpus/${TARGET}") + set(_seed_dir "${CMAKE_CURRENT_BINARY_DIR}/seeds/${TARGET}") + set(_artifact_dir "${CMAKE_CURRENT_BINARY_DIR}/artifacts/${TARGET}/") + set(_common_seed_dir "${CMAKE_CURRENT_BINARY_DIR}/seeds/common") + + if (NOT TARGET ccmath-fuzz-seeds) + ccmath_ensure_fuzz_seeds("${_common_seed_dir}") + if (EXISTS "${CCMATH_FUZZ_SHARED_SEED_DIR}") + file(GLOB _shared_seeds CONFIGURE_DEPENDS "${CCMATH_FUZZ_SHARED_SEED_DIR}/*.bin") + foreach (_seed IN LISTS _shared_seeds) + get_filename_component(_seed_name "${_seed}" NAME) + configure_file("${_seed}" "${_common_seed_dir}/${_seed_name}" COPYONLY) + endforeach () + endif () + add_custom_target(ccmath-fuzz-seeds) + endif () + + file(MAKE_DIRECTORY "${_seed_dir}") + file(GLOB _common_seed_files CONFIGURE_DEPENDS "${_common_seed_dir}/*.bin") + foreach (_seed IN LISTS _common_seed_files) + get_filename_component(_seed_name "${_seed}" NAME) + configure_file("${_seed}" "${_seed_dir}/${_seed_name}" COPYONLY) + endforeach () + add_dependencies(${TARGET} ccmath-fuzz-seeds) + + add_test( + NAME ${TARGET}-smoke + COMMAND ${CMAKE_COMMAND} + -DCORPUS_DIR=${_corpus_dir} + -DSEED_DIR=${_seed_dir} + -DARTIFACT_PREFIX=${_artifact_dir} + -DFUZZER=$ + -DCCMATH_FUZZ_DICT=${CCMATH_FUZZ_DICT} + -P ${CCMATH_FUZZ_ROOT}/cmake/run_fuzz_smoke.cmake) + set_tests_properties(${TARGET}-smoke PROPERTIES TIMEOUT 300 LABELS "fuzz") + + add_custom_target(${TARGET}-campaign + COMMAND ${CMAKE_COMMAND} + -DFUZZER=$ + -DCORPUS_DIR=${_corpus_dir} + -DSEED_DIR=${_seed_dir} + -DARTIFACT_PREFIX=${_artifact_dir} + -DCCMATH_FUZZ_DICT=${CCMATH_FUZZ_DICT} + -P ${CCMATH_FUZZ_ROOT}/cmake/run_fuzz_campaign.cmake + USES_TERMINAL + COMMENT "Running libFuzzer campaign for ${TARGET}") + add_dependencies(${TARGET}-campaign ${TARGET}) +endfunction() + +function(ccmath_add_fuzz_module TARGET) + set(options) + set(oneValueArgs MODULE) + set(multiValueArgs SOURCES) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_MODULE) + message(FATAL_ERROR "ccmath_add_fuzz_module(${TARGET}): MODULE is required") + endif () + + if (CCM_SOURCES) + ccmath_add_fuzz_target(${TARGET} MODULE ${CCM_MODULE} SOURCES ${CCM_SOURCES}) + else () + ccmath_add_fuzz_target(${TARGET} MODULE ${CCM_MODULE}) + endif () +endfunction() diff --git a/cmake/helpers/CcmOptionalTooling.cmake b/cmake/helpers/CcmOptionalTooling.cmake new file mode 100644 index 00000000..6d25b5d5 --- /dev/null +++ b/cmake/helpers/CcmOptionalTooling.cmake @@ -0,0 +1,82 @@ +# Optional proof-tool discovery for tests/rigorous proof smoke targets. + +function(ccmath_find_optional_proof_tools) + find_package(Python3 COMPONENTS Interpreter QUIET) + find_program(CCMATH_GAPPA_EXECUTABLE gappa) + find_program(CCMATH_SOLLYA_EXECUTABLE sollya) + + # FindPython3 exposes these as regular variables, so lift them out of the + # helper scope before downstream checks and custom targets consume them. + set(Python3_Interpreter_FOUND "${Python3_Interpreter_FOUND}" PARENT_SCOPE) + set(Python3_EXECUTABLE "${Python3_EXECUTABLE}" PARENT_SCOPE) +endfunction() + +function(ccmath_missing_optional_tools OUT_VAR) + ccmath_find_optional_proof_tools() + set(_missing "") + if (NOT Python3_Interpreter_FOUND AND NOT Python3_EXECUTABLE) + list(APPEND _missing "Python3") + endif () + if (NOT CCMATH_GAPPA_EXECUTABLE) + list(APPEND _missing "gappa") + endif () + if (NOT CCMATH_SOLLYA_EXECUTABLE) + list(APPEND _missing "sollya") + endif () + set(${OUT_VAR} "${_missing}" PARENT_SCOPE) +endfunction() + +function(ccmath_warn_missing_optional_tools CONTEXT) + ccmath_missing_optional_tools(_missing) + if (_missing) + string(REPLACE ";" ", " _missing_text "${_missing}") + message(WARNING "CCMath ${CONTEXT}: ${_missing_text} not found; target skipped") + endif () +endfunction() + +function(ccmath_add_pow_proof_smoke_target) + ccmath_missing_optional_tools(_missing) + ccmath_warn_missing_optional_tools("pow proof smoke") + + set(_script ${CMAKE_CURRENT_SOURCE_DIR}/../proofs/math/power/proof_freshness_check.py) + set(_workdir ${CMAKE_CURRENT_SOURCE_DIR}/..) + + if (_missing) + string(REPLACE ";" ", " _missing_text "${_missing}") + add_custom_target(ccmath-rigorous-pow-proof-smoke + COMMAND ${CMAKE_COMMAND} -E echo "error: ${_missing_text} not found" + COMMAND ${CMAKE_COMMAND} -E false + COMMENT "pow proof smoke (${_missing_text} missing)") + else () + add_custom_target(ccmath-rigorous-pow-proof-smoke + COMMAND ${Python3_EXECUTABLE} ${_script} --smoke-only --require-tools + WORKING_DIRECTORY ${_workdir} + COMMENT "pow proof smoke") + endif () +endfunction() + +function(ccmath_add_pow_proof_ulp_aspiration_target) + ccmath_missing_optional_tools(_missing) + ccmath_warn_missing_optional_tools("pow proof ULP aspiration") + + set(_script ${CMAKE_CURRENT_SOURCE_DIR}/../proofs/math/power/proof_freshness_check.py) + set(_workdir ${CMAKE_CURRENT_SOURCE_DIR}/..) + + if (_missing) + string(REPLACE ";" ", " _missing_text "${_missing}") + add_custom_target(ccmath-rigorous-pow-proof-ulp-aspiration + COMMAND ${CMAKE_COMMAND} -E echo "ccmath-rigorous-pow-proof-ulp-aspiration skipped: ${_missing_text} not found" + COMMENT "pow proof ULP aspiration (${_missing_text} missing)") + else () + add_custom_target(ccmath-rigorous-pow-proof-ulp-aspiration + COMMAND ${Python3_EXECUTABLE} ${_script} --aspirational-ulp + WORKING_DIRECTORY ${_workdir} + COMMENT "pow proof aspirational 0.5 and 1.5 ULP checks (non-blocking)") + + add_test(NAME ccmath-rigorous-pow-proof-ulp-aspiration + COMMAND ${Python3_EXECUTABLE} ${_script} --aspirational-ulp + WORKING_DIRECTORY ${_workdir}) + set_tests_properties(ccmath-rigorous-pow-proof-ulp-aspiration PROPERTIES + LABELS "aspirational;proof") + endif () +endfunction() diff --git a/cmake/helpers/CcmOracleHelpers.cmake b/cmake/helpers/CcmOracleHelpers.cmake new file mode 100644 index 00000000..69498df4 --- /dev/null +++ b/cmake/helpers/CcmOracleHelpers.cmake @@ -0,0 +1,115 @@ +# Rigorous oracle campaign registration (registry-driven executables and ctests). +# +# Include OracleCampaignRegistry.cmake before calling ccmath_apply_oracle_registries(). + +include_guard(GLOBAL) + +function(_ccmath_oracle_test_requires TEST_PREFIX OUT_VAR) + set(_requires_var ${TEST_PREFIX}_REQUIRES) + if (DEFINED ${_requires_var}) + set(${OUT_VAR} ${${_requires_var}} PARENT_SCOPE) + else () + set(${OUT_VAR} "" PARENT_SCOPE) + endif () +endfunction() + +function(_ccmath_oracle_test_condition_ok REQUIRES_EXPR) + if (REQUIRES_EXPR STREQUAL "") + set(_ok TRUE PARENT_SCOPE) + return () + endif () + if (${REQUIRES_EXPR}) + set(_ok TRUE PARENT_SCOPE) + else () + set(_ok FALSE PARENT_SCOPE) + endif () +endfunction() + +function(_ccmath_oracle_register_ctest BACKEND TEST_ID LOG_DIR) + set(_prefix CCMATH_ORACLE_${BACKEND}_TEST_${TEST_ID}) + + if (NOT DEFINED ${_prefix}_NAME) + message(FATAL_ERROR "Oracle registry: missing ${_prefix}_NAME") + endif () + if (NOT DEFINED ${_prefix}_TARGET) + message(FATAL_ERROR "Oracle registry: missing ${_prefix}_TARGET") + endif () + + _ccmath_oracle_test_requires(${_prefix} _requires) + _ccmath_oracle_test_condition_ok("${_requires}" _register) + if (NOT _register) + return () + endif () + + set(_ctest_args) + if (DEFINED ${_prefix}_ARGS) + list(APPEND _ctest_args ${${_prefix}_ARGS}) + endif () + + set(_log_var ${_prefix}_LOG_OUTPUT) + set(_json_var ${_prefix}_JSON_OUTPUT) + if (DEFINED ${_log_var}) + ccmath_add_oracle_ctest(${${_prefix}_NAME} + TARGET ${${_prefix}_TARGET} + LABELS ${${_prefix}_LABELS} + TIMEOUT ${${_prefix}_TIMEOUT} + ARGS ${_ctest_args} + LOG_OUTPUT ${${_log_var}} + WORKING_DIRECTORY ${LOG_DIR}) + elseif (DEFINED ${_json_var}) + ccmath_add_oracle_ctest(${${_prefix}_NAME} + TARGET ${${_prefix}_TARGET} + LABELS ${${_prefix}_LABELS} + TIMEOUT ${${_prefix}_TIMEOUT} + ARGS ${_ctest_args} + JSON_OUTPUT ${${_json_var}} + WORKING_DIRECTORY ${LOG_DIR}) + else () + ccmath_add_oracle_ctest(${${_prefix}_NAME} + TARGET ${${_prefix}_TARGET} + LABELS ${${_prefix}_LABELS} + TIMEOUT ${${_prefix}_TIMEOUT} + ARGS ${_ctest_args} + WORKING_DIRECTORY ${LOG_DIR}) + endif () +endfunction() + +function(_ccmath_oracle_register_backend BACKEND LOG_DIR) + set(_exe_ids_var CCMATH_ORACLE_${BACKEND}_EXECUTABLE_IDS) + if (NOT DEFINED ${_exe_ids_var}) + return () + endif () + + foreach (_exe_id IN LISTS ${_exe_ids_var}) + set(_target_var CCMATH_ORACLE_${BACKEND}_${_exe_id}_TARGET) + set(_source_var CCMATH_ORACLE_${BACKEND}_${_exe_id}_SOURCE) + if (NOT DEFINED ${_target_var} OR NOT DEFINED ${_source_var}) + message(FATAL_ERROR "Oracle registry: incomplete entry for ${BACKEND} ${_exe_id}") + endif () + + if (BACKEND STREQUAL "MPFR") + ccmath_ensure_mpfr_oracle_target(${${_target_var}} SOURCES ${${_source_var}}) + elseif (BACKEND STREQUAL "COREMATH") + ccmath_ensure_coremath_oracle_target(${${_target_var}} SOURCES ${${_source_var}}) + else () + message(FATAL_ERROR "Unknown oracle backend ${BACKEND}") + endif () + + set(_ctest_ids_var CCMATH_ORACLE_${BACKEND}_${_exe_id}_CTESTS) + if (DEFINED ${_ctest_ids_var}) + foreach (_test_id IN LISTS ${_ctest_ids_var}) + _ccmath_oracle_register_ctest(${BACKEND} ${_test_id} ${LOG_DIR}) + endforeach () + endif () + endforeach () +endfunction() + +function(ccmath_apply_oracle_registries LOG_DIR) + if (CCMATH_ENABLE_MPFR_TESTS AND CCMATH_MPFR_FOUND) + _ccmath_oracle_register_backend(MPFR ${LOG_DIR}) + endif () + + if (CCMATH_ENABLE_COREMATH_TESTS AND CCMATH_COREMATH_FOUND AND TARGET ccmath::coremath_oracle) + _ccmath_oracle_register_backend(COREMATH ${LOG_DIR}) + endif () +endfunction() diff --git a/cmake/helpers/CcmTestHelpers.cmake b/cmake/helpers/CcmTestHelpers.cmake new file mode 100644 index 00000000..451ac3cd --- /dev/null +++ b/cmake/helpers/CcmTestHelpers.cmake @@ -0,0 +1,283 @@ +# Helpers for registering ccmath GTest suites, oracle executables, and script tests. +# Layout follows LLVM-libc style: sources under tests/src mirror include/ccmath, +# shared harness under tests/shared, wiring under tests/unit and tests/rigorous. + +include_guard(GLOBAL) + +if (NOT DEFINED CCMATH_TESTS_ROOT) + message(FATAL_ERROR "CCMATH_TESTS_ROOT must be set before including CcmTestHelpers.cmake") +endif () + +function(ccmath_module_rel_dir MODULE OUT_VAR) + if (MODULE STREQUAL "ext") + set(_rel src/ext) + elseif (MODULE MATCHES "^internal/") + set(_rel src/math/${MODULE}) + else () + set(_rel src/math/${MODULE}) + endif () + set(${OUT_VAR} ${_rel} PARENT_SCOPE) +endfunction() + +function(ccmath_collect_test_sources SOURCE_DIR OUT_VAR) + set(options) + set(oneValueArgs) + set(multiValueArgs EXCLUDE PATTERNS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (IS_ABSOLUTE "${SOURCE_DIR}") + set(_abs_dir "${SOURCE_DIR}") + else () + set(_abs_dir "${CCMATH_TESTS_ROOT}/${SOURCE_DIR}") + endif () + + if (NOT EXISTS "${_abs_dir}") + message(FATAL_ERROR "ccmath_collect_test_sources: directory does not exist: ${_abs_dir}") + endif () + + if (NOT CCM_PATTERNS) + set(CCM_PATTERNS "*_test.cpp" "lerp_test_*.cpp") + endif () + + set(_candidates "") + foreach (_pattern IN LISTS CCM_PATTERNS) + file(GLOB _matches + LIST_DIRECTORIES false + RELATIVE "${CCMATH_TESTS_ROOT}" + "${_abs_dir}/${_pattern}") + list(APPEND _candidates ${_matches}) + endforeach () + + if (CCM_EXCLUDE) + foreach (_path IN LISTS CCM_EXCLUDE) + list(REMOVE_ITEM _candidates "${_path}") + endforeach () + endif () + + list(REMOVE_DUPLICATES _candidates) + list(SORT _candidates) + set(${OUT_VAR} ${_candidates} PARENT_SCOPE) +endfunction() + +function(ccmath_prefix_test_sources REL_PREFIX SOURCES OUT_VAR) + set(_prefixed "") + foreach (_source IN LISTS SOURCES) + list(APPEND _prefixed "${REL_PREFIX}${_source}") + endforeach () + set(${OUT_VAR} ${_prefixed} PARENT_SCOPE) +endfunction() + +function(ccmath_add_gtest_suite TARGET) + set(options) + set(oneValueArgs) + set(multiValueArgs SOURCES LABELS COMPILE_DEFINITIONS COMPILE_OPTIONS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_gtest_suite(${TARGET}): SOURCES is required") + endif () + + add_executable(${TARGET}) + target_sources(${TARGET} PRIVATE ${CCM_SOURCES}) + target_link_libraries(${TARGET} PRIVATE ccmath::test) + add_test(NAME ${TARGET} COMMAND ${TARGET}) + + if (CCM_LABELS) + set_tests_properties(${TARGET} PROPERTIES LABELS "${CCM_LABELS}") + endif () + if (CCM_COMPILE_DEFINITIONS) + target_compile_definitions(${TARGET} PRIVATE ${CCM_COMPILE_DEFINITIONS}) + endif () + if (CCM_COMPILE_OPTIONS) + target_compile_options(${TARGET} PRIVATE ${CCM_COMPILE_OPTIONS}) + endif () +endfunction() + +function(ccmath_add_command_gtest TARGET) + set(options) + set(oneValueArgs) + set(multiValueArgs SOURCES LABELS COMPILE_DEFINITIONS COMPILE_OPTIONS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_command_gtest(${TARGET}): SOURCES is required") + endif () + + add_executable(${TARGET} ${CCM_SOURCES}) + target_link_libraries(${TARGET} PRIVATE ccmath::ccmath ccmath::ext) + target_include_directories(${TARGET} PRIVATE + ${CCMATH_TESTS_ROOT} + ${CCMATH_TESTS_ROOT}/shared + ${CCMATH_TESTS_ROOT}/..) + target_compile_features(${TARGET} PRIVATE cxx_std_17) + add_test(NAME ${TARGET} COMMAND ${TARGET}) + + if (CCM_LABELS) + set_tests_properties(${TARGET} PROPERTIES LABELS "${CCM_LABELS}") + endif () + if (CCM_COMPILE_DEFINITIONS) + target_compile_definitions(${TARGET} PRIVATE ${CCM_COMPILE_DEFINITIONS}) + endif () + if (CCM_COMPILE_OPTIONS) + target_compile_options(${TARGET} PRIVATE ${CCM_COMPILE_OPTIONS}) + endif () +endfunction() + +function(ccmath_add_unit_module) + set(options) + set(oneValueArgs TARGET MODULE) + set(multiValueArgs LABELS EXTRA_SOURCES EXCLUDE PATTERNS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_TARGET OR NOT CCM_MODULE) + message(FATAL_ERROR "ccmath_add_unit_module requires TARGET and MODULE") + endif () + + ccmath_module_rel_dir(${CCM_MODULE} _rel_dir) + ccmath_collect_test_sources(${_rel_dir} _sources + EXCLUDE ${CCM_EXCLUDE} + PATTERNS ${CCM_PATTERNS}) + ccmath_prefix_test_sources("../" "${_sources}" _prefixed) + + if (CCM_EXTRA_SOURCES) + list(APPEND _prefixed ${CCM_EXTRA_SOURCES}) + endif () + + if (NOT _prefixed) + message(FATAL_ERROR "ccmath_add_unit_module(${CCM_TARGET}): no test sources in ${_rel_dir}") + endif () + + ccmath_add_gtest_suite(${CCM_TARGET} SOURCES ${_prefixed} LABELS ${CCM_LABELS}) +endfunction() + +function(ccmath_add_rigorous_gtest_suite TARGET) + set(options APPLY_POW_VALIDATION) + set(oneValueArgs) + set(multiValueArgs SOURCES LABELS COMPILE_DEFINITIONS COMPILE_OPTIONS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_SOURCES) + message(FATAL_ERROR "ccmath_add_rigorous_gtest_suite(${TARGET}): SOURCES is required") + endif () + + if (NOT CCM_LABELS) + set(CCM_LABELS rigorous) + endif () + + ccmath_add_gtest_suite(${TARGET} + SOURCES ${CCM_SOURCES} + LABELS ${CCM_LABELS} + COMPILE_DEFINITIONS ${CCM_COMPILE_DEFINITIONS} + COMPILE_OPTIONS ${CCM_COMPILE_OPTIONS}) + + if (CCM_APPLY_POW_VALIDATION) + if (NOT COMMAND ccmath_apply_pow_validation_options) + message(FATAL_ERROR "ccmath_add_rigorous_gtest_suite(${TARGET}): include TestValidationOptions.cmake first") + endif () + ccmath_apply_pow_validation_options(${TARGET}) + endif () +endfunction() + +function(ccmath_add_rigorous_module_suite TARGET) + set(options APPLY_POW_VALIDATION) + set(oneValueArgs MODULE) + set(multiValueArgs LABELS EXCLUDE PATTERNS EXTRA_SOURCES) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_MODULE) + message(FATAL_ERROR "ccmath_add_rigorous_module_suite(${TARGET}): MODULE is required") + endif () + + ccmath_module_rel_dir(${CCM_MODULE} _rel_dir) + ccmath_collect_test_sources(${_rel_dir} _sources + EXCLUDE ${CCM_EXCLUDE} + PATTERNS ${CCM_PATTERNS}) + ccmath_prefix_test_sources("../" "${_sources}" _prefixed) + + if (CCM_EXTRA_SOURCES) + list(APPEND _prefixed ${CCM_EXTRA_SOURCES}) + endif () + + if (NOT _prefixed) + message(FATAL_ERROR "ccmath_add_rigorous_module_suite(${TARGET}): no test sources in ${_rel_dir}") + endif () + + if (CCM_APPLY_POW_VALIDATION) + ccmath_add_rigorous_gtest_suite(${TARGET} + SOURCES ${_prefixed} + LABELS ${CCM_LABELS} + APPLY_POW_VALIDATION) + else () + ccmath_add_rigorous_gtest_suite(${TARGET} + SOURCES ${_prefixed} + LABELS ${CCM_LABELS}) + endif () +endfunction() + +function(ccmath_add_conformance_suite TARGET) + set(options APPLY_POW_VALIDATION) + set(oneValueArgs) + set(multiValueArgs EXCLUDE EXTRA_SOURCES) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + ccmath_collect_test_sources(conformance _sources EXCLUDE ${CCM_EXCLUDE}) + ccmath_prefix_test_sources("../" "${_sources}" _prefixed) + + if (CCM_EXTRA_SOURCES) + list(APPEND _prefixed ${CCM_EXTRA_SOURCES}) + endif () + + if (CCM_APPLY_POW_VALIDATION) + ccmath_add_rigorous_gtest_suite(${TARGET} + SOURCES ${_prefixed} + APPLY_POW_VALIDATION) + else () + ccmath_add_rigorous_gtest_suite(${TARGET} SOURCES ${_prefixed}) + endif () +endfunction() + +function(ccmath_add_python_script_test TEST_NAME) + set(options OPTIONAL) + set(oneValueArgs SCRIPT WORKING_DIRECTORY TIMEOUT) + set(multiValueArgs LABELS ARGS) + cmake_parse_arguments(CCM "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if (NOT CCM_SCRIPT) + message(FATAL_ERROR "ccmath_add_python_script_test(${TEST_NAME}): SCRIPT is required") + endif () + if (NOT Python3_EXECUTABLE) + if (CCM_OPTIONAL) + message(WARNING "CCMath: Python3 not found; ${TEST_NAME} skipped") + return () + endif () + message(FATAL_ERROR "ccmath_add_python_script_test(${TEST_NAME}): Python3 not available") + endif () + + set(_command ${Python3_EXECUTABLE} ${CCM_SCRIPT}) + if (CCM_ARGS) + list(APPEND _command ${CCM_ARGS}) + endif () + + if (NOT CCM_WORKING_DIRECTORY) + set(CCM_WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + endif () + + add_test(NAME ${TEST_NAME} COMMAND ${_command}) + set_tests_properties(${TEST_NAME} PROPERTIES WORKING_DIRECTORY "${CCM_WORKING_DIRECTORY}") + + if (CCM_LABELS) + set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${CCM_LABELS}") + endif () + if (CCM_TIMEOUT) + set_tests_properties(${TEST_NAME} PROPERTIES TIMEOUT ${CCM_TIMEOUT}) + endif () +endfunction() + +# Back-compat aliases used by older CMakeLists fragments. +function(ccmath_add_test TEST_TARGET) + ccmath_add_gtest_suite(${TEST_TARGET} SOURCES ${ARGN}) +endfunction() + +function(ccmath_add_command_test TEST_TARGET) + ccmath_add_command_gtest(${TEST_TARGET} SOURCES ${ARGN}) +endfunction() diff --git a/cmake/in/version.hpp.in b/cmake/in/version.hpp.in index ce6713af..cf6b4f22 100644 --- a/cmake/in/version.hpp.in +++ b/cmake/in/version.hpp.in @@ -14,7 +14,8 @@ #define CCMATH_VERSION_MAJOR @CCMATH_VERSION_MAJOR@ #define CCMATH_VERSION_MINOR @CCMATH_VERSION_MINOR@ #define CCMATH_VERSION_PATCH @CCMATH_VERSION_PATCH@ -#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) +// MAJOR.MINOR.PATCH packed into one integer, up to 3 digits (0 to 999) per component. +#define CCMATH_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 1000000 + (MINOR) * 1000 + (PATCH)) #define CCMATH_VERSION CCMATH_TO_VERSION(CCMATH_VERSION_MAJOR, CCMATH_VERSION_MINOR, CCMATH_VERSION_PATCH) #endif // CCMATH_VERSION_HPP \ No newline at end of file diff --git a/cmake/modules/CcmLibFuzzer.cmake b/cmake/modules/CcmLibFuzzer.cmake index fb946a12..6350ce1d 100644 --- a/cmake/modules/CcmLibFuzzer.cmake +++ b/cmake/modules/CcmLibFuzzer.cmake @@ -6,7 +6,7 @@ # CCMATH_FUZZ_LINK_FLAGS - link options for fuzz executables # # Optional environment: -# LIB_FUZZING_ENGINE - OSS-Fuzz / external engine path (link-only; compile uses fuzzer-no-link) +# LIB_FUZZING_ENGINE - OSS-Fuzz / external engine path (link-only, compile uses fuzzer-no-link) include(CheckCXXSourceCompiles) @@ -15,12 +15,22 @@ set(CCMATH_FUZZ_SANITIZERS "fuzzer,address,undefined") function(ccmath_probe_libfuzzer compile_flags link_flags result_var) set(CMAKE_REQUIRED_FLAGS ${compile_flags}) set(CMAKE_REQUIRED_LINK_OPTIONS ${link_flags}) - check_cxx_source_compiles(" + # With -fsanitize=fuzzer the runtime supplies main(). With fuzzer-no-link, the probe needs its own. + if (compile_flags MATCHES "fuzzer-no-link") + set(_probe_source " #include #include extern \"C\" int LLVMFuzzerTestOneInput(uint8_t const*, size_t) { return 0; } int main() { return 0; } -" ${result_var}) +") + else () + set(_probe_source " +#include +#include +extern \"C\" int LLVMFuzzerTestOneInput(uint8_t const*, size_t) { return 0; } +") + endif () + check_cxx_source_compiles("${_probe_source}" ${result_var}) endfunction() function(ccmath_append_compiler_libcxx_flags compile_flags_var link_flags_var) @@ -47,7 +57,7 @@ function(ccmath_append_compiler_libcxx_flags compile_flags_var link_flags_var) endfunction() function(ccmath_detect_libfuzzer) - set(_compile_base "-fno-omit-frame-pointer" "-O1") + set(_compile_base "-fno-omit-frame-pointer" "-O1" "-fno-sanitize-recover=all") set(_available FALSE) if (DEFINED ENV{LIB_FUZZING_ENGINE}) diff --git a/cmake/vcpkg/vcpkg.json b/cmake/vcpkg/vcpkg.json new file mode 100644 index 00000000..34ec0a9f --- /dev/null +++ b/cmake/vcpkg/vcpkg.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "ccmath-validation", + "version-string": "0.3.0", + "builtin-baseline": "10ceb139a610ebf3c6aa49cdc4a4b7f3db5d3f2b", + "dependencies": [ + "mpfr", + "pkgconf" + ] +} diff --git a/docs/ROUNDING_STATUS.md b/docs/ROUNDING_STATUS.md new file mode 100644 index 00000000..591b8bf8 --- /dev/null +++ b/docs/ROUNDING_STATUS.md @@ -0,0 +1,91 @@ +## Rounding Status + +Status for the four IEEE rounding modes. x = runtime handles that mode. Empty = not applicable or not implemented. + +| Feature | FE_TONEAREST? | FE_UPWARD? | FE_DOWNWARD? | FE_TOWARDZERO? | Notes? | +|----------------|---------------|------------|--------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------| +| fabs | x | x | x | x | | +| fdim | x | x | x | x | | +| fma | x | x | x | x | Native-first on validated AArch64 runtime paths; constexpr follows `CCM_CONSTEXPR_ROUNDING_MODE`; `fmal` remains target-format dependent | +| (f)max | x | x | x | x | | +| (f)min | x | x | x | x | | +| remainder | x | x | x | x | | +| remquo | x | x | x | x | | +| nan | | | | | | +| fpclassify | | | | | | +| isfinite | | | | | | +| isgreater | | | | | | +| isgreaterequal | | | | | | +| isinf | | | | | | +| isless | | | | | | +| islessequal | | | | | | +| islessgreater | | | | | | +| isnan | | | | | | +| isnormal | | | | | | +| isunordered | | | | | | +| signbit | | | | | | +| exp | x | x | x | x | | +| exp2 | x | x | x | x | | +| expm1 | x | x | x | x | | +| log | x | x | x | x | | +| log1p | x | x | x | x | | +| log2 | x | x | x | x | | +| log10 | x | x | x | x | | +| copysign | x | x | x | x | | +| frexp | x | x | x | x | | +| ilogb | x | x | x | x | | +| ldexp | x | x | x | x | | +| logb | x | x | x | x | | +| modf | x | x | x | x | | +| nextafter | x | x | x | x | | +| nexttoward | x | x | x | x | | +| scalbn | x | x | x | x | | +| acosh | | | | | | +| asinh | | | | | | +| atanh | | | | | | +| cosh | | | | | | +| sinh | | | | | | +| tanh | | | | | | +| ceil | x | x | x | x | | +| floor | x | x | x | x | | +| nearbyint | x | x | x | x | | +| rint | x | x | x | x | | +| round | x | x | x | x | | +| trunc | x | x | x | x | | +| cbrt | x | x | x | x | | +| hypot | x | x | x | x | | +| pow | x | x | x | x | CORE-MATH all-mode campaigns on covered inputs | +| sqrt | x | x | x | x | | +| assoc_laguerre | | | | | | +| assoc_legendre | | | | | | +| beta | | | | | | +| comp_ellint_1 | | | | | | +| comp_ellint_2 | | | | | | +| comp_ellint_3 | | | | | | +| cyl_bessel_i | | | | | | +| cyl_bessel_j | | | | | | +| cyl_bessel_k | | | | | | +| cyl_neumann | | | | | | +| ellint_1 | | | | | | +| ellint_2 | | | | | | +| ellint_3 | | | | | | +| expint | | | | | | +| hermite | | | | | | +| laguerre | | | | | | +| legendre | | | | | | +| riemann_zeta | | | | | | +| sph_bessel | | | | | | +| sph_legendre | | | | | | +| sph_neumann | | | | | | +| acos | x | x | x | x | | +| asin | x | x | x | x | | +| atan | x | x | x | x | | +| atan2 | x | x | x | x | | +| cos | x | x | x | x | | +| sin | x | x | x | x | | +| tan | x | x | x | x | | +| gamma | x | x | x | x | | +| lerp | x | x | x | x | | +| lgamma | x | x | x | x | | + +> Last Updated: June 7, 2026 diff --git a/docs/STATUS.md b/docs/STATUS.md index 7eceae16..216f6fce 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,107 +1,108 @@ ## Implementation Progress (Modules) -| Module | % done | In Progress? | Notes? | Planned Completion Version | -|------------------------------|--------|--------------|------------------------------------------|----------------------------| -| Basic | 100 | | remquo `long double` delegates to double | v0.1.0 (Released) | -| Compare | 100 | | | v0.1.0 (Released) | -| Exponential | 98 | | | v0.2.0 (Released) | -| Float Manipulation | 100 | | scalbn `long double` is slow | v0.2.0 (Released) | -| Hyperbolic | 0 | | Empty headers only | | -| Nearest | 100 | | | v0.2.0 (Released) | -| Power | 96 | | | v0.2.0 (Released) | -| Special Functions | 0 | | Empty headers only | | -| Trigonometric | 100 | | | v0.2.0 (Released) | -| Misc Functions | 97 | | | v0.2.0 (Released) | -| **Total Library Completion** | **68** | | | v0.2.0 (Released) | +| Module | % done | In Progress? | Notes? | Planned Completion Version | +|------------------------------|--------|--------------|------------------------------------------------------------------------------------------|----------------------------| +| Basic | 100 | | remquo `long double` delegates to double | v0.1.0 (Released) | +| Compare | 100 | | | v0.1.0 (Released) | +| Exponential | 98 | | | v0.2.0 (Released) | +| Float Manipulation | 100 | | scalbn `long double` is slow | v0.2.0 (Released) | +| Hyperbolic | 0 | | Empty headers only | | +| Nearest | 100 | | | v0.2.0 (Released) | +| Power | 96 | | Path forcing, MPFR path-matrix, powf reduced domains, powl characterization, proof-smoke | v0.2.0 (Released) | +| Special Functions | 0 | | Empty headers only | | +| Trigonometric | 100 | | | v0.2.0 (Released) | +| Misc Functions | 97 | | | v0.2.0 (Released) | +| **Total Library Completion** | **68** | | | N/a | -> Last Updated: May 31, 2026 +> Last Updated: June 7, 2026 ## Implementation Progress (Functions) -| Feature | % done | TODO | -|----------------|--------|----------------------------------| -| fabs | 100 | | -| fdim | 100 | | -| fma | 100 | | -| (f)max | 100 | | -| (f)min | 100 | | -| remainder | 100 | | -| remquo | 100 | remquol delegates through double | -| nan | 100 | Brittle on non-IEC559 platforms | -| fpclassify | 100 | | -| isfinite | 100 | | -| isgreater | 100 | | -| isgreaterequal | 100 | | -| isinf | 100 | | -| isless | 100 | | -| islessequal | 100 | | -| islessgreater | 100 | | -| isnan | 100 | | -| isnormal | 100 | | -| isunordered | 100 | | -| signbit | 100 | | -| exp | 98 | | -| exp2 | 98 | | -| expm1 | 98 | | -| log | 98 | | -| log1p | 98 | | -| log2 | 98 | | -| log10 | 98 | | -| copysign | 100 | | -| frexp | 100 | | -| ilogb | 100 | | -| ldexp | 100 | | -| logb | 100 | | -| modf | 100 | | -| nextafter | 100 | | -| nexttoward | 100 | | -| scalbn | 100 | Long double is slow. | -| acosh | 0 | Empty header | -| asinh | 0 | Empty header | -| atanh | 0 | Empty header | -| cosh | 0 | Empty header | -| sinh | 0 | Empty header | -| tanh | 0 | Empty header | -| ceil | 100 | | -| floor | 100 | | -| nearbyint | 100 | Runtime rounding mode via `fenv` | -| rint | 100 | | -| round | 100 | | -| trunc | 100 | | -| cbrt | 98 | | -| hypot | 100 | | -| pow | 88 | | -| sqrt | 100 | | -| assoc_laguerre | 0 | Empty header | -| assoc_legendre | 0 | Empty header | -| beta | 0 | Empty header | -| comp_ellint_1 | 0 | Empty header | -| comp_ellint_2 | 0 | Empty header | -| comp_ellint_3 | 0 | Empty header | -| cyl_bessel_i | 0 | Empty header | -| cyl_bessel_j | 0 | Empty header | -| cyl_bessel_k | 0 | Empty header | -| cyl_neumann | 0 | Empty header | -| ellint_1 | 0 | Empty header | -| ellint_2 | 0 | Empty header | -| ellint_3 | 0 | Empty header | -| expint | 0 | Empty header | -| hermite | 0 | Empty header | -| laguerre | 0 | Empty header | -| legendre | 0 | Empty header | -| riemann_zeta | 0 | Empty header | -| sph_bessel | 0 | Empty header | -| sph_legendre | 0 | Empty header | -| sph_neumann | 0 | Empty header | -| acos | 100 | | -| asin | 100 | | -| atan | 100 | | -| atan2 | 100 | | -| cos | 100 | | -| sin | 100 | | -| tan | 100 | | -| gamma | 100 | | -| lerp | 90 | | -| lgamma | 100 | | +Per-function rounding mode status: [ROUNDING_STATUS.md](ROUNDING_STATUS.md). -> Last Updated: May 31, 2026 +| Feature | % done | TODO | +|----------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| fabs | 100 | | +| fdim | 100 | | +| fma | 100 | Native-first AArch64 runtime, exact software fallback for all modes, constexpr respects `CCM_CONSTEXPR_ROUNDING_MODE`. Wider x86_64 validation remains open. | +| (f)max | 100 | | +| (f)min | 100 | | +| remainder | 100 | | +| remquo | 100 | remquol delegates through double | +| nan | 100 | Brittle on non-IEC559 platforms | +| fpclassify | 100 | | +| isfinite | 100 | | +| isgreater | 100 | | +| isgreaterequal | 100 | | +| isinf | 100 | | +| isless | 100 | | +| islessequal | 100 | | +| islessgreater | 100 | | +| isnan | 100 | | +| isnormal | 100 | | +| isunordered | 100 | | +| signbit | 100 | | +| exp | 98 | | +| exp2 | 98 | | +| expm1 | 98 | | +| log | 98 | | +| log1p | 98 | | +| log2 | 98 | | +| log10 | 98 | | +| copysign | 100 | | +| frexp | 100 | | +| ilogb | 100 | | +| ldexp | 100 | | +| logb | 100 | | +| modf | 100 | | +| nextafter | 100 | | +| nexttoward | 100 | | +| scalbn | 100 | Long double is slow. | +| acosh | 0 | Empty header | +| asinh | 0 | Empty header | +| atanh | 0 | Empty header | +| cosh | 0 | Empty header | +| sinh | 0 | Empty header | +| tanh | 0 | Empty header | +| ceil | 100 | | +| floor | 100 | | +| nearbyint | 100 | Runtime rounding mode via `fenv` | +| rint | 100 | | +| round | 100 | | +| trunc | 100 | | +| cbrt | 98 | | +| hypot | 100 | | +| pow | 88 | Rigorous MPFR `public_default` campaigns, path-matrix tooling, powf reduced domains, powl characterization. Full binary64 proof and independent extended-precision `powl` validation remain open | +| sqrt | 100 | | +| assoc_laguerre | 0 | Empty header | +| assoc_legendre | 0 | Empty header | +| beta | 0 | Empty header | +| comp_ellint_1 | 0 | Empty header | +| comp_ellint_2 | 0 | Empty header | +| comp_ellint_3 | 0 | Empty header | +| cyl_bessel_i | 0 | Empty header | +| cyl_bessel_j | 0 | Empty header | +| cyl_bessel_k | 0 | Empty header | +| cyl_neumann | 0 | Empty header | +| ellint_1 | 0 | Empty header | +| ellint_2 | 0 | Empty header | +| ellint_3 | 0 | Empty header | +| expint | 0 | Empty header | +| hermite | 0 | Empty header | +| laguerre | 0 | Empty header | +| legendre | 0 | Empty header | +| riemann_zeta | 0 | Empty header | +| sph_bessel | 0 | Empty header | +| sph_legendre | 0 | Empty header | +| sph_neumann | 0 | Empty header | +| acos | 100 | | +| asin | 100 | | +| atan | 100 | | +| atan2 | 100 | | +| cos | 100 | | +| sin | 100 | | +| tan | 100 | | +| gamma | 100 | | +| lerp | 90 | | +| lgamma | 100 | | +> Last Updated: June 7, 2026 diff --git a/docs/ULP_STATUS.md b/docs/ULP_STATUS.md new file mode 100644 index 00000000..3ffa0b90 --- /dev/null +++ b/docs/ULP_STATUS.md @@ -0,0 +1,303 @@ +# ULP status + +The goal for ccmath is correctly rounded results in all four IEEE 754 rounding modes for every +function. We are not there yet, and this page is meant to be an honest picture of where each +function actually stands rather than a promise. It reports what was measured, how it was measured, +and what is still left to do. + +The companion page ROUNDING_STATUS.md tracks which rounding modes each function handles at runtime. +This page is about accuracy, the distance between the ccm result and the true correctly rounded +value. + +## How to read this + +Every function falls into one of five classes. + +| Class | Meaning | +|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Exact | Produces the exact representable result, so 0 ULP in all four modes by construction. The nearest-integer, sign, and scaling families are here. | +| Correctly rounded | The unique nearest representable value, 0.5 ULP, confirmed by an all-modes campaign. sqrt is correctly rounded by IEEE 754, and fma is exact-then-rounded. | +| Bounded | Measured to be within a small number of ULP of the correctly rounded value. Correct rounding is the goal, and these functions are not there yet. | +| Not a rounding function | Returns a bool, an int, or a classification, so ULP does not apply. The comparison family is here. | +| Not implemented | The header exists but there is no implementation yet. The hyperbolic and special-function families are here. | + +A few notes on the numbers in the tables below. + +These numbers are for ccmath's own generic kernels, not for whatever the public function lowers to +at runtime. On a clang or gcc build the public functions hand the runtime round-to-nearest case to +a compiler builtin, which is the system libm and is often more accurate than the figures here. We +measure the generic kernel on purpose, because it is ccmath's own code and it is what runs at +compile time, in the directed rounding modes, in deterministic mode, and anywhere a builtin is not +available. So treat these as the portable worst case ccmath is responsible for, not as what a clang +or gcc runtime build delivers in round-to-nearest. + +Max ULP is the largest error observed between the kernel result and the correctly rounded value +over the measured corpus. A value of 0 means every result was the nearest representable value. The +reference is MPFR evaluated at high precision and rounded once to the target type, so it is the +true correctly rounded answer and not another library's result. + +The accuracy families, which are Exponential, Power, Trigonometric, and Miscellaneous, break each +function into its three type variants: the float f-suffix variant (expf, powf, sinf), the plain double +variant (exp, pow, sin), and the long double variant (expl, powl, sinl). The other families are exact +or not implemented, which holds for every type, so they stay one row each. + +Only binary32 is measured so far. Every binary32 number comes from an exhaustive sweep over all bit +patterns, which makes its max a proven worst case in round-to-nearest. binary64 is a larger job and is +measured next, and long double after that, so both show up as pending until then. + +Each variant row names the exact kernel it runs in the Tested kernel column. The gen kernels are +templates, so the same symbol instantiates at each type, for example ccm::gen::pow_gen and +ccm::gen::pow_gen. The asin, acos, atan, tgamma, and lgamma kernels come as a separate float +and double function with no dedicated long double kernel, so their long double variant uses the double +kernel. These are ccmath's own generic kernels under --impl=gen, the implementation the public function +falls back to in the directed modes, at compile time, and in deterministic mode. The same harness can +point at the public path or the system libm with --impl=public or --impl=libm, but those are not what +this page reports. + +The numbers are measured in round-to-nearest, the default mode. Which rounding modes each function +handles at runtime is tracked separately in ROUNDING_STATUS.md. + +To refresh these tables, run the sweep and then the generator. + +``` +tools/measure_ulp.sh --exhaustive-float +python3 tools/ulp_status.py +``` + +The function classes and the list of measured functions live in tools/ulp_status_registry.json. +A function with no completed campaign summary shows up as pending, so the tables never claim a +number that was not actually measured. + +## Accuracy by function + + + +> ccmath generic kernels (--impl=gen), round-to-nearest. MPFR correctly-rounded reference. The accuracy families break +> each function into its float, double, and long double variant (expf, exp, expl). binary32 is measured by an exhaustive +> sweep over all bit patterns, so its max is a proven worst case. binary64 and long double are pending. The Tested kernel +> column names the exact symbol that runs. Measured on apple with clang 17.0.0 (clang-1700.4.4.1). Last refreshed +> 2026-06-18T12:49:45Z. + +### Basic + +| Function | Class | Max ULP | Avg ULP | Notes | +|-----------|-------------------------|---------|---------|-----------------------------------------------------------------------------------------------------------| +| fabs | Exact | 0 | 0 | | +| fdim | Exact | 0 | 0 | | +| fma | Correctly rounded | 0 | 0 | Native FMA where trusted, exact software fallback otherwise. Validated by the MPFR fma all-mode campaign. | +| fmax | Exact | 0 | 0 | | +| fmin | Exact | 0 | 0 | | +| fmod | Exact | 0 | 0 | Exact remainder by construction. | +| remainder | Exact | 0 | 0 | | +| remquo | Exact | 0 | 0 | | +| nan | Not a rounding function | n/a | n/a | Constructs a NaN payload. No rounded result. | + +### Comparison + +| Function | Class | Max ULP | Avg ULP | Notes | +|----------------|-------------------------|---------|---------|-------| +| fpclassify | Not a rounding function | n/a | n/a | | +| isfinite | Not a rounding function | n/a | n/a | | +| isgreater | Not a rounding function | n/a | n/a | | +| isgreaterequal | Not a rounding function | n/a | n/a | | +| isinf | Not a rounding function | n/a | n/a | | +| isless | Not a rounding function | n/a | n/a | | +| islessequal | Not a rounding function | n/a | n/a | | +| islessgreater | Not a rounding function | n/a | n/a | | +| isnan | Not a rounding function | n/a | n/a | | +| isnormal | Not a rounding function | n/a | n/a | | +| isunordered | Not a rounding function | n/a | n/a | | +| signbit | Not a rounding function | n/a | n/a | | + +### Exponential + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|---------|---------|----------|----------------------------------|-------| +| expf (binary32) | Bounded | 1 | 7.62e-05 | ccm::gen::exp_gen | | +| exp (binary64) | Bounded | pending | pending | ccm::gen::exp_gen | | +| expl (long double) | Bounded | pending | pending | ccm::gen::exp_gen | | +| exp2f (binary32) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| exp2 (binary64) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| exp2l (long double) | Bounded | pending | pending | ccm::gen::exp2_gen | | +| expm1f (binary32) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| expm1 (binary64) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| expm1l (long double) | Bounded | pending | pending | ccm::gen::expm1_gen | | +| logf (binary32) | Bounded | pending | pending | ccm::gen::log_gen | | +| log (binary64) | Bounded | pending | pending | ccm::gen::log_gen | | +| logl (long double) | Bounded | pending | pending | ccm::gen::log_gen | | +| log1pf (binary32) | Bounded | 2 | 0.00453 | ccm::gen::log1p_gen | | +| log1p (binary64) | Bounded | pending | pending | ccm::gen::log1p_gen | | +| log1pl (long double) | Bounded | pending | pending | ccm::gen::log1p_gen | | +| log2f (binary32) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log2 (binary64) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log2l (long double) | Bounded | pending | pending | ccm::gen::log2_gen | | +| log10f (binary32) | Bounded | pending | pending | ccm::gen::log10_gen | | +| log10 (binary64) | Bounded | pending | pending | ccm::gen::log10_gen | | +| log10l (long double) | Bounded | pending | pending | ccm::gen::log10_gen | | + +### Float manipulation + +| Function | Class | Max ULP | Avg ULP | Notes | +|------------|-------------------------|---------|---------|--------------------------------| +| copysign | Exact | 0 | 0 | | +| frexp | Exact | 0 | 0 | | +| ilogb | Not a rounding function | n/a | n/a | Returns an integer exponent. | +| ldexp | Exact | 0 | 0 | | +| logb | Exact | 0 | 0 | | +| modf | Exact | 0 | 0 | | +| nextafter | Exact | 0 | 0 | | +| nexttoward | Exact | 0 | 0 | | +| nextup | Exact | 0 | 0 | | +| nextdown | Exact | 0 | 0 | | +| scalbn | Exact | 0 | 0 | long double is slow but exact. | + +### Hyperbolic + +| Function | Class | Max ULP | Avg ULP | Notes | +|----------|-----------------|----------|----------|-------| +| acosh | Not implemented | not impl | not impl | | +| asinh | Not implemented | not impl | not impl | | +| atanh | Not implemented | not impl | not impl | | +| cosh | Not implemented | not impl | not impl | | +| sinh | Not implemented | not impl | not impl | | +| tanh | Not implemented | not impl | not impl | | + +### Nearest integer + +| Function | Class | Max ULP | Avg ULP | Notes | +|-----------|-------|---------|---------|-------| +| ceil | Exact | 0 | 0 | | +| floor | Exact | 0 | 0 | | +| nearbyint | Exact | 0 | 0 | | +| rint | Exact | 0 | 0 | | +| round | Exact | 0 | 0 | | +| trunc | Exact | 0 | 0 | | + +### Power + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|-------------------|---------|---------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| cbrtf (binary32) | Bounded | 1 | 0.296 | ccm::gen::cbrt_gen | | +| cbrt (binary64) | Bounded | pending | pending | ccm::gen::cbrt_gen | | +| cbrtl (long double) | Bounded | pending | pending | ccm::gen::cbrt_gen | | +| hypotf (binary32) | Bounded | pending | pending | | Binary argument. Measurement is pending a binary sweep harness. | +| hypot (binary64) | Bounded | pending | pending | | | +| hypotl (long double) | Bounded | pending | pending | | | +| powf (binary32) | Bounded | 0 | 0 | ccm::gen::pow_gen | Binary, so sampled over the finite-result band and broad bit patterns rather than exhaustive. See the pow campaign appendix below. | +| pow (binary64) | Bounded | pending | pending | ccm::gen::pow_gen | | +| powl (long double) | Bounded | pending | pending | ccm::gen::pow_gen | | +| sqrtf (binary32) | Correctly rounded | 0 | 0 | ccm::gen::sqrt_gen | Correctly rounded by IEEE 754. | +| sqrt (binary64) | Correctly rounded | pending | pending | ccm::gen::sqrt_gen | | +| sqrtl (long double) | Correctly rounded | pending | pending | ccm::gen::sqrt_gen | | + +### Special functions + +| Function | Class | Max ULP | Avg ULP | Notes | +|----------------|-----------------|----------|----------|-------| +| assoc_laguerre | Not implemented | not impl | not impl | | +| assoc_legendre | Not implemented | not impl | not impl | | +| beta | Not implemented | not impl | not impl | | +| comp_ellint_1 | Not implemented | not impl | not impl | | +| comp_ellint_2 | Not implemented | not impl | not impl | | +| comp_ellint_3 | Not implemented | not impl | not impl | | +| cyl_bessel_i | Not implemented | not impl | not impl | | +| cyl_bessel_j | Not implemented | not impl | not impl | | +| cyl_bessel_k | Not implemented | not impl | not impl | | +| cyl_neumann | Not implemented | not impl | not impl | | +| ellint_1 | Not implemented | not impl | not impl | | +| ellint_2 | Not implemented | not impl | not impl | | +| ellint_3 | Not implemented | not impl | not impl | | +| expint | Not implemented | not impl | not impl | | +| hermite | Not implemented | not impl | not impl | | +| laguerre | Not implemented | not impl | not impl | | +| legendre | Not implemented | not impl | not impl | | +| riemann_zeta | Not implemented | not impl | not impl | | +| sph_bessel | Not implemented | not impl | not impl | | +| sph_legendre | Not implemented | not impl | not impl | | +| sph_neumann | Not implemented | not impl | not impl | | + +### Trigonometric + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|----------------------|---------|---------|----------|----------------------------------|-----------------------------------------------------------------| +| acosf (binary32) | Bounded | pending | pending | ccm::internal::impl::acos_float | | +| acos (binary64) | Bounded | pending | pending | ccm::internal::impl::acos_double | | +| acosl (long double) | Bounded | pending | pending | ccm::internal::impl::acos_double | | +| asinf (binary32) | Bounded | pending | pending | ccm::internal::impl::asin_float | | +| asin (binary64) | Bounded | pending | pending | ccm::internal::impl::asin_double | | +| asinl (long double) | Bounded | pending | pending | ccm::internal::impl::asin_double | | +| atanf (binary32) | Bounded | 1 | 4.67e-10 | ccm::internal::impl::atan_float | | +| atan (binary64) | Bounded | pending | pending | ccm::internal::impl::atan_double | | +| atanl (long double) | Bounded | pending | pending | ccm::internal::impl::atan_double | | +| atan2f (binary32) | Bounded | pending | pending | | Binary argument. Measurement is pending a binary sweep harness. | +| atan2 (binary64) | Bounded | pending | pending | | | +| atan2l (long double) | Bounded | pending | pending | | | +| cosf (binary32) | Bounded | pending | pending | ccm::gen::cos_gen | | +| cos (binary64) | Bounded | pending | pending | ccm::gen::cos_gen | | +| cosl (long double) | Bounded | pending | pending | ccm::gen::cos_gen | | +| sinf (binary32) | Bounded | pending | pending | ccm::gen::sin_gen | | +| sin (binary64) | Bounded | pending | pending | ccm::gen::sin_gen | | +| sinl (long double) | Bounded | pending | pending | ccm::gen::sin_gen | | +| tanf (binary32) | Bounded | pending | pending | ccm::gen::tan_gen | | +| tan (binary64) | Bounded | pending | pending | ccm::gen::tan_gen | | +| tanl (long double) | Bounded | pending | pending | ccm::gen::tan_gen | | + +### Miscellaneous + +| Variant | Class | Max ULP | Avg ULP | Tested kernel | Notes | +|-----------------------|---------|---------|---------|------------------------------|---------------------------------------------------------------------------------------------------------------------| +| tgammaf (binary32) | Bounded | pending | pending | ccm::internal::gamma_float | Listed as gamma in earlier status docs. | +| tgamma (binary64) | Bounded | pending | pending | ccm::internal::gamma_double | | +| tgammal (long double) | Bounded | pending | pending | ccm::internal::gamma_double | | +| lerp (binary32) | Bounded | pending | pending | | Ternary argument, and a C++ template with no f or l suffix variant. Measurement is pending a ternary sweep harness. | +| lerp (binary64) | Bounded | pending | pending | | | +| lerp (long double) | Bounded | pending | pending | | | +| lgammaf (binary32) | Bounded | pending | pending | ccm::internal::lgamma_float | | +| lgamma (binary64) | Bounded | pending | pending | ccm::internal::lgamma_double | | +| lgammal (long double) | Bounded | pending | pending | ccm::internal::lgamma_double | | + + + +## Scope and caveats + +This pass measures binary32. binary64 is the next job, and long double after that, so both show up as +pending. The accuracy families list all three variants so the gaps are explicit rather than implied. + +The atan2, hypot, and lerp functions take more than one argument, so they wait on a binary and +ternary sweep and are marked pending. pow is measured separately, and its detailed campaign is in +the appendix below. + +Near a zero or a pole, ULP error is governed by conditioning rather than by rounding, because a +tiny absolute error sits across many representable values when the result itself is near zero or +very large. Functions with poles or zeros in range, such as tan near its poles and lgamma near its +zeros at 1 and 2, can show a large max and average that reflect how close the corpus landed to +those spots rather than the kernel's typical accuracy. + +## Appendix: pow campaign detail + +Rigorous oracle quick campaigns (apple aarch64, clang 17, June 7 2026). Not proofs. Max ULP is the +same in all four IEEE rounding modes on the corpora below. + +**Failure columns** + +| Column | Meaning | +|--------|---------| +| MPFR hard failures | Cases that exceed the campaign 4 ULP ceiling, or disagree with MPFR on NaN, infinity, or signed zero. Within-ceiling results that miss the 0.5 ULP correctly-rounded target are tracked separately and are not counted here. | +| CORE-MATH bit mismatches | Cases where ccm bits differ from the CORE-MATH cr_* reference in the active rounding mode. This is not a ULP threshold. Cases where a higher-precision cross-check shows ccm is correct and the CORE-MATH oracle is wrong are excluded. | + +Per-case logs with base, exponent, actual, and expected bit patterns live under +[tests/rigorous/oracle_logs/README.md](../tests/rigorous/oracle_logs/README.md). Re-run the +rigorous oracle ctest targets to refresh them. + +| Function | Configuration | MPFR cases | MPFR Max ULP | MPFR hard failures | CORE-MATH cases | CORE-MATH Max ULP | CORE-MATH bit mismatches | Notes | +|-----------|---------------------------------------------|------------|--------------|--------------------|-----------------|-------------------|--------------------------|-----------------------------------------------------------------------------------| +| ccm::pow | public_default | 3132 | 1 | 0 | 2068 | 1 | 59 | MPFR structured binary64 corpus. CORE-MATH finite regular corpus. | +| ccm::pow | runtime_no_builtin, generic_modeled_domain | N/a | N/a | 0 | N/a | N/a | N/a | Configuration-matrix quick campaign, no-builtin build only. | +| ccm::powf | public_default | 68036 | 1 | 2 | 62372 | 1 | 2050 | Structured binary32 corpus. Directional modes carry most CORE-MATH mismatches. | +| ccm::powf | Reduced domains | 6144 | 1 | 0 | 6144 | 1 | 396 | Mantissa/subnormal finite domains. Not evidence for NaN, infinity, or zero cases. | +| ccm::powl | ld64 alias | 144 | 0 | 0 | N/a | N/a | N/a | Double-shaped platform. ld64_conservative corpus. | +| ccm::powl | ld80 special + bounded int + general finite | N/a | N/a | N/a | N/a | N/a | N/a | Not exercised on double-shaped platforms. | +| ccm::powl | ld128 / unknown strict | N/a | N/a | N/a | N/a | N/a | N/a | Detection and NaN policy tests only. | +| ccm::powl | reduced-precision fallback | N/a | N/a | N/a | N/a | N/a | N/a | Only when CCMATH_ENABLE_REDUCED_PRECISION_POWL=ON. | + +> Last Updated: June 17, 2026 diff --git a/docs/approximating_functions/ACM-Reference-Format.bst b/docs/approximating_functions/ACM-Reference-Format.bst new file mode 100644 index 00000000..ac35934b --- /dev/null +++ b/docs/approximating_functions/ACM-Reference-Format.bst @@ -0,0 +1,3224 @@ +%%% -*-BibTeX-*- +%%% ==================================================================== +%%% @BibTeX-style-file{ +%%% author = "Nelson H. F. Beebe, Boris Veytsman and Gerald Murray", +%%% version = "2.1", +%%% acmart-version = "1.90", +%%% date = "Mar 26 2023", +%%% filename = "ACM-Reference-Format.bst", +%%% email = "borisv@lk.net, boris@varphi.com", +%%% codetable = "ISO/ASCII", +%%% keywords = "ACM Transactions bibliography style; BibTeX", +%%% license = "public domain", +%%% supported = "yes", +%%% abstract = "", +%%% } +%%% ==================================================================== + +%%% Revision history: see source in git + +ENTRY + { address + advisor + archiveprefix + author + booktitle + chapter + city + date + edition + editor + eprint + eprinttype + eprintclass + howpublished + institution + journal + key + location + month + note + number + organization + pages + primaryclass + publisher + school + series + title + type + volume + year + % New keys recognized + issue % UTAH: used in, e.g., ACM SIGSAM Bulletin and ACM Communications in Computer Algebra + articleno + eid + day % UTAH: needed for newspapers, weeklies, bi-weeklies + doi % UTAH + url % UTAH + bookpages % UTAH + numpages + lastaccessed % UTAH: used only for @Misc{...} + coden % UTAH + isbn % UTAH + isbn-13 % UTAH + issn % UTAH + lccn % UTAH + distinctURL % whether to print url if doi is present + archived % Where the url is archived + venue % for presentations + } + {} + { label.year extra.label sort.year sort.label basic.label.year} + +INTEGERS { output.state before.all mid.sentence after.sentence after.block } + +INTEGERS { show-isbn-10-and-13 } % initialized below in begin.bib + +INTEGERS { nameptr namesleft numnames } + +INTEGERS { multiresult } + +INTEGERS { len } + +INTEGERS { last.extra.num } + +STRINGS { s t t.org u } + +STRINGS { last.label next.extra } + +STRINGS { p1 p2 p3 page.count } + + +FUNCTION { not } +{ + { #0 } + { #1 } + if$ +} + +FUNCTION { and } +{ + 'skip$ + { pop$ #0 } + if$ +} + +FUNCTION { or } +{ + { pop$ #1 } + 'skip$ + if$ +} + + +FUNCTION { dump.stack.1 } +{ + duplicate$ "STACK[top] = [" swap$ * "]" * warning$ +} + +FUNCTION { dump.stack.2 } +{ + duplicate$ "STACK[top ] = [" swap$ * "]" * warning$ + swap$ + duplicate$ "STACK[top-1] = [" swap$ * "]" * warning$ + swap$ +} + +FUNCTION { empty.or.unknown } +{ + %% Examine the top stack entry, and push 1 if it is empty, or + %% consists only of whitespace, or is a string beginning with two + %% queries (??), and otherwise, push 0. + %% + %% This function provides a replacement for empty$, with the + %% convenient feature that unknown values marked by two leading + %% queries are treated the same as missing values, and thus, do not + %% appear in the output .bbl file, and yet, their presence in .bib + %% file(s) serves to mark values which are temporarily missing, but + %% are expected to be filled in eventually once more data is + %% obtained. The TeX User Group and BibNet bibliography archives + %% make extensive use of this practice. + %% + %% An empty string cannot serve the same purpose, because just as in + %% statistics data processing, an unknown value is not the same as an + %% empty value. + %% + %% At entry: stack = ... top:[string] + %% At exit: stack = ... top:[0 or 1] + + duplicate$ empty$ + { pop$ #1 } + { #1 #2 substring$ "??" = } + if$ +} + +FUNCTION { empty.or.zero } +{ + %% Examine the top entry and push 1 if it is empty, or is zero + duplicate$ empty$ + { pop$ #1 } + { "0" = } + if$ +} + + +FUNCTION { writeln } +{ + %% In BibTeX style files, the sequences + %% + %% ... "one" "two" output + %% ... "one" "two" output.xxx + %% + %% ship "one" to the output file, possibly following by punctuation, + %% leaving the stack with + %% + %% ... "two" + %% + %% There is thus a one-string lag in output processing that must be + %% carefully handled to avoid duplicating a string in the output + %% file. Unless otherwise noted, all output.xxx functions leave + %% just one new string on the stack, and that model should be born + %% in mind when reading or writing function code. + %% + %% BibTeX's asynchronous buffering of output from strings from the + %% stack is confusing because newline$ bypasses the buffer. It + %% would have been so much easier for newline to be a character + %% rather than a state of the output-in-progress. + %% + %% The documentation in btxhak.dvi is WRONG: it says + %% + %% newline$ Writes onto the bbl file what's accumulated in the + %% output buffer. It writes a blank line if and only + %% if the output buffer is empty. Since write$ does + %% reasonable line breaking, you should use this + %% function only when you want a blank line or an + %% explicit line break. + %% + %% write$ Pops the top (string) literal and writes it on the + %% output buffer (which will result in stuff being + %% written onto the bbl file when the buffer fills + %% up). + %% + %% Examination of the BibTeX source code shows that write$ does + %% indeed behave as claimed, but newline$ sends a newline character + %% directly to the output file, leaving the stack unchanged. The + %% first line "Writes onto ... buffer." is therefore wrong. + %% + %% The original BibTeX style files almost always use "write$ newline$" + %% in that order, so it makes sense to hide that pair in a private + %% function like this one, named after a statement in Pascal, + %% the programming language embedded in the BibTeX Web program. + + write$ % output top-of-stack string + newline$ % immediate write of newline (not via stack) +} + +FUNCTION { init.state.consts } +{ + #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := +} + +FUNCTION { output.nonnull } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + ", " * write$ + } + { + output.state after.block = + { + add.period$ writeln + "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.dot.space } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = % { ". " * write$ } + { + ". " * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.remove } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + " " * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.removenospace } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + "" * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output } +{ % discard top token if empty, else like output.nonnull + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION { output.dot.space } +{ % discard top token if empty, else like output.nonnull.dot.space + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull.dot.space + if$ +} + +FUNCTION { output.removenospace } +{ % discard top token if empty, else like output.nonnull.removenospace + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull.removenospace + if$ +} + +FUNCTION { output.check } +{ % like output, but warn if key name on top-of-stack is not set + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +FUNCTION { bibinfo.output.check } +{ % like output.check, adding bibinfo field + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + { "\bibinfo{" t "}{" * * swap$ * "}" * + output.nonnull } + if$ +} + +FUNCTION { output.check.dot.space } +{ % like output.dot.space, but warn if key name on top-of-stack is not set + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull.dot.space + if$ +} + +FUNCTION { fin.block } +{ % functionally, but not logically, identical to fin.entry + add.period$ + writeln +} + +FUNCTION { fin.entry } +{ + add.period$ + writeln +} + +FUNCTION { new.sentence } +{ % update sentence state, with neither output nor stack change + output.state after.block = + 'skip$ + { + output.state before.all = + 'skip$ + { after.sentence 'output.state := } + if$ + } + if$ +} + +FUNCTION { fin.sentence } +{ + add.period$ + write$ + new.sentence + "" +} + +FUNCTION { new.block } +{ + output.state before.all = + 'skip$ + { after.block 'output.state := } + if$ +} + +FUNCTION { output.coden } % UTAH +{ % output non-empty CODEN as one-line sentence (stack untouched) + coden empty.or.unknown + { } + { "\showCODEN{" coden * "}" * writeln } + if$ +} + +% +% Sometimes articleno starts with the word 'Article' or 'Paper. +% (this is a bug of acmdl, sigh) +% We strip them. We assume eid or articleno is already on stack +% + +FUNCTION { strip.articleno.or.eid } +{ + 't := + t #1 #7 substring$ "Article" = + {t #8 t text.length$ substring$ 't :=} + { } + if$ + t #1 #7 substring$ "article" = + {t #8 t text.length$ substring$ 't :=} + { } + if$ + t #1 #5 substring$ "Paper" = + {t #6 t text.length$ substring$ 't :=} + { } + if$ + t #1 #5 substring$ "paper" = + {t #6 t text.length$ substring$ 't :=} + { } + if$ + % Strip any left trailing space or ~ + t #1 #1 substring$ " " = + {t #2 t text.length$ substring$ 't :=} + { } + if$ + t #1 #1 substring$ "~" = + {t #2 t text.length$ substring$ 't :=} + { } + if$ + t +} + + +FUNCTION { format.articleno } +{ + articleno empty.or.unknown not eid empty.or.unknown not and + { "Both articleno and eid are defined for " cite$ * warning$ } + 'skip$ + if$ + articleno empty.or.unknown eid empty.or.unknown and + { "" } + { + numpages empty.or.unknown + { "articleno or eid field, but no numpages field, in " + cite$ * warning$ } + { } + if$ + eid empty.or.unknown + { "Article \bibinfo{articleno}{" articleno strip.articleno.or.eid * "}" * } + { "Article \bibinfo{articleno}{" eid strip.articleno.or.eid * "}" * } + if$ + } + if$ +} + +FUNCTION { format.year } +{ % push year string or "[n.\,d.]" onto output stack + %% Because year is a mandatory field, we always force SOMETHING + %% to be output. If we do not know year but know date, we push date + %% as the last resort + "\bibinfo{year}{" + year empty.or.unknown + { date empty.or.unknown + { "[n.\,d.]" } + { date #1 #4 substring$ } + if$ + } + { year } + if$ + * "}" * +} + +FUNCTION { format.day.month } +{ % push "day month " or "month " or "" onto output stack + day empty.or.unknown + { + month empty.or.unknown + { "" } + { "\bibinfo{date}{" month * "} " *} + if$ + } + { + month empty.or.unknown + { "" } + { "\bibinfo{date}{" day * " " * month * "} " *} + if$ + } + if$ +} + +FUNCTION { format.day.month.year } % UTAH +{ % if month is empty, push "" else push "(MON.)" or "(DD MON.)" + % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) + % acm-*.bst addition: prefix parenthesized date string with + % ", Article nnn " + articleno empty.or.unknown eid empty.or.unknown and + { "" } + { output.state after.block = + {", " format.articleno * } + { format.articleno } + if$ + } + if$ + " (" * format.day.month * format.year * ")" * +} + +FUNCTION { output.day.month.year } % UTAH +{ % if month is empty value, do nothing; else output stack top and + % leave with new top string "(MON.)" or "(DD MON.)" + % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) + format.day.month.year + output.nonnull.remove +} + +FUNCTION { strip.doi } % UTAH +{ % Strip any Web address prefix to recover the bare DOI, leaving the + % result on the output stack, as recommended by CrossRef DOI + % documentation. + % For example, reduce "http://doi.acm.org/10.1145/1534530.1534545" to + % "10.1145/1534530.1534545". A suitable URL is later typeset and + % displayed as the LAST item in the reference list entry. Publisher Web + % sites wrap this with a suitable link to a real URL to resolve the DOI, + % and the master https://doi.org/ address is preferred, since publisher- + % specific URLs can disappear in response to economic events. All + % journals are encouraged by the DOI authorities to use that typeset + % format and link procedures for uniformity across all publications that + % include DOIs in reference lists. + % The numeric prefix is guaranteed to start with "10.", so we use + % that as a test. + % 2017-02-04 Added stripping of https:// (Boris) + doi #1 #3 substring$ "10." = + { doi } + { + doi 't := % get modifiable copy of DOI + + % Change https:// to http:// to strip both prefixes (BV) + + t #1 #8 substring$ "https://" = + { "http://" t #9 t text.length$ #8 - substring$ * 't := } + { } + if$ + + t #1 #7 substring$ "http://" = + { + t #8 t text.length$ #7 - substring$ 't := + + "INTERNAL STYLE-FILE ERROR" 's := + + % search for next "/" and assign its suffix to s + + { t text.length$ } + { + t #1 #1 substring$ "/" = + { + % save rest of string as true DOI (should be 10.xxxx/yyyy) + t #2 t text.length$ #1 - substring$ 's := + "" 't := % empty string t terminates the loop + } + { + % discard first character and continue loop: t <= substring(t,2,last) + t #2 t text.length$ #1 - substring$ 't := + } + if$ + } + while$ + + % check for valid DOI (should be 10.xxxx/yyyy) + s #1 #3 substring$ "10." = + { } + { "unrecognized DOI substring " s * " in DOI value [" * doi * "]" * warning$ } + if$ + + s % push the stripped DOI on the output stack + + } + { + "unrecognized DOI value [" doi * "]" * warning$ + doi % push the unrecognized original DOI on the output stack + } + if$ + } + if$ +} + +% +% Change by BV: added standard prefix to URL +% +FUNCTION { output.doi } % UTAH +{ % output non-empty DOI as one-line sentence (stack untouched) + doi empty.or.unknown + { } + { + "\href{https://doi.org/" strip.doi * "}{doi:\nolinkurl{" * + strip.doi * "}}" * writeln + } + if$ +} + +FUNCTION { output.isbn } % UTAH +{ % output non-empty ISBN-10 and/or ISBN-13 as one-line sentences (stack untouched) + show-isbn-10-and-13 + { + %% show both 10- and 13-digit ISBNs + isbn empty.or.unknown + { } + { + "\showISBNx{" isbn * "}" * writeln + } + if$ + isbn-13 empty.or.unknown + { } + { + "\showISBNxiii{" isbn-13 * "}" * writeln + } + if$ + } + { + %% show 10-digit ISBNs only if 13-digit ISBNs not available + isbn-13 empty.or.unknown + { + isbn empty.or.unknown + { } + { + "\showISBNx{" isbn * "}" * writeln + } + if$ + } + { + "\showISBNxiii{" isbn-13 * "}" * writeln + } + if$ + } + if$ +} + +FUNCTION { output.issn } % UTAH +{ % output non-empty ISSN as one-line sentence (stack untouched) + issn empty.or.unknown + { } + { "\showISSN{" issn * "}" * writeln } + if$ +} + +FUNCTION { output.issue } +{ % output non-empty issue number as a one-line sentence (stack untouched) + issue empty.or.unknown + { } + { "Issue " issue * "." * writeln } + if$ +} + +FUNCTION { output.lccn } % UTAH +{ % return with stack untouched + lccn empty.or.unknown + { } + { "\showLCCN{" lccn * "}" * writeln } + if$ +} + +FUNCTION { output.note } % UTAH +{ % return with stack empty + note empty.or.unknown + { } + { "\shownote{" note * "}" add.period$ * writeln } + if$ +} + +FUNCTION { output.note.check } % UTAH +{ % return with stack empty + note empty.or.unknown + { "empty note in " cite$ * warning$ } + { "\shownote{" note * "}" add.period$ * writeln } + if$ +} + +FUNCTION { output.eprint } % +{ % return with stack empty + eprint empty.or.unknown + { } + { "\showeprint" + archiveprefix empty.or.unknown + { eprinttype empty.or.unknown + { } + { "[" eprinttype "]" * * * } + if$ + } + { "[" archiveprefix "l" change.case$ "]" * * * } + if$ + "{" eprint "}" * * * + primaryclass empty.or.unknown + { eprintclass empty.or.unknown + { } + { "~[" eprintclass "]" * * * } + if$ + } + { "~[" primaryclass "]" * * * } + if$ + writeln + } + if$ +} + + +% +% Changes by BV 2011/04/15. Do not output +% url if doi is defined +% +% +% Changes by BV 2021/11/26. Output url even if doi is defined +% if distinctURL is not zero. +% +FUNCTION { output.url } % UTAH +{ % return with stack untouched + % output URL and associated lastaccessed fields + doi empty.or.unknown distinctURL empty.or.zero not or + { + url empty.or.unknown + { } + { new.sentence + %% Use \urldef, outside \showURL, so that %nn, #, etc in URLs work + %% correctly. Put the actual URL on its own line to reduce the + %% likelihood of BibTeX's nasty line wrapping after column 79. + %% \url{} can undo this, but if that doesn't work for some reason + %% the .bbl file would have to be repaired manually. + "\urldef\tempurl%" writeln + "\url{" url * "}" * writeln + + "\showURL{%" writeln + lastaccessed empty.or.unknown + { "" } + { "Retrieved " lastaccessed * " from " * } + if$ + "\tempurl" * + archived empty.or.unknown + { } + {"\urldef\tempurla%" writeln + "\url{" archived * "}%" * writeln + ", archived at [\tempurla]" * + } + if$ + "}" * writeln + } + if$ + } + { } + if$ +} + +FUNCTION { output.year.check } +{ % warn if year empty, output top string and leave " YEAR