From 3cf29ed45da5f6688be589b6394f0aa60bc7d059 Mon Sep 17 00:00:00 2001 From: sentomk Date: Thu, 30 Apr 2026 20:06:54 +0800 Subject: [PATCH 1/3] infra: literal-match bench expansion, compile-time benchmarks, CI regression gate --- .github/workflows/bench-compare.yml | 47 +++ .github/workflows/ci-pr.yml | 33 +- .github/workflows/ci.yml | 43 +-- .github/workflows/clang-format-auto.yml | 2 +- bench/CMakeLists.txt | 2 + bench/bench_ptn_lit.cpp | 17 +- bench/bench_suite.cpp | 357 ++++++++++++++++++--- bench/compile-time/CMakeLists.txt | 93 ++++++ bench/compile-time/ct_bench_compound.cpp | 28 ++ bench/compile-time/ct_bench_lit_128.cpp | 144 +++++++++ bench/compile-time/ct_bench_lit_16.cpp | 32 ++ bench/compile-time/ct_bench_lit_32.cpp | 48 +++ bench/compile-time/ct_bench_lit_64.cpp | 80 +++++ bench/compile-time/ct_bench_lit_8.cpp | 24 ++ bench/compile-time/ct_bench_variant_32.cpp | 84 +++++ packaging/vcpkg/portfile.cmake | 2 +- scripts/bench_ct_compare.py | 234 ++++++++++++++ 17 files changed, 1187 insertions(+), 83 deletions(-) create mode 100644 bench/compile-time/CMakeLists.txt create mode 100644 bench/compile-time/ct_bench_compound.cpp create mode 100644 bench/compile-time/ct_bench_lit_128.cpp create mode 100644 bench/compile-time/ct_bench_lit_16.cpp create mode 100644 bench/compile-time/ct_bench_lit_32.cpp create mode 100644 bench/compile-time/ct_bench_lit_64.cpp create mode 100644 bench/compile-time/ct_bench_lit_8.cpp create mode 100644 bench/compile-time/ct_bench_variant_32.cpp create mode 100644 scripts/bench_ct_compare.py diff --git a/.github/workflows/bench-compare.yml b/.github/workflows/bench-compare.yml index 9f6dcd2..eff99c1 100644 --- a/.github/workflows/bench-compare.yml +++ b/.github/workflows/bench-compare.yml @@ -120,3 +120,50 @@ jobs: run: | echo "Benchmark regression gate failed." exit 1 + + ct-compare: + runs-on: ubuntu-latest + if: always() + + steps: + - name: Checkout baseline + uses: actions/checkout@v4 + with: + ref: ${{ inputs.baseline_ref }} + path: baseline + + - name: Checkout current + uses: actions/checkout@v4 + with: + path: current + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Run baseline compile-time benchmark + run: | + python current/scripts/bench_ct_compare.py \ + --source-dir baseline \ + --build-dir baseline/build_ct \ + --json-out baseline/build_ct/ct_result.json + + - name: Run current compile-time benchmark + run: | + python current/scripts/bench_ct_compare.py \ + --source-dir current \ + --build-dir current/build_ct \ + --baseline-json baseline/build_ct/ct_result.json \ + --json-out current/build_ct/ct_result.json \ + --fail-if-regress-pct "${{ inputs.fail_if_regress_pct }}" \ + --fail-if-mean-regress-pct "${{ inputs.fail_if_mean_regress_pct }}" + + - name: Upload compile-time benchmark artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: bench-ct-compare-${{ github.run_id }} + path: | + baseline/build_ct/ct_result.json + current/build_ct/ct_result.json diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 3d7df30..35af7bb 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -2,7 +2,7 @@ name: patternia PR Check (Ubuntu) on: pull_request: - branches: [main, dev-main] + branches: [main] types: [opened, synchronize, reopened, ready_for_review] concurrency: @@ -45,3 +45,34 @@ jobs: with: name: test-log path: build/Testing/Temporary/LastTest.log + + ct-bench-build: + name: Ubuntu / Clang / Compile-Time Bench Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Clang + run: | + sudo apt-get update + sudo apt-get install -y clang + echo "CXX=clang++" >> $GITHUB_ENV + clang++ --version + + - name: Configure compile-time benchmarks + run: | + cmake -S . -B build_ct \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_COMPILER=${{ env.CXX }} \ + -DPTN_BUILD_TESTS=OFF \ + -DPTN_BUILD_BENCHMARKS=ON \ + -DPTN_SKIP_COMPILER_CHECK=ON + + - name: Build compile-time benchmarks + run: cmake --build build_ct --target ptn_bench_ct --parallel + + - name: Report build time + if: always() + run: | + echo "Compile-time benchmarks built successfully." diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aeef32e..a8b14b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: patternia CI on: push: - branches: [main, dev-main] + branches: [main] paths-ignore: - "README.md" - "CONTRIBUTING.md" @@ -10,7 +10,7 @@ on: - "LICENSE" - ".github/ISSUE_TEMPLATE/**" pull_request: - branches: [main, dev-main] + branches: [main] paths-ignore: - "README.md" - "CONTRIBUTING.md" @@ -93,9 +93,10 @@ jobs: runs-on: ubuntu-latest permissions: contents: write - pull-requests: write steps: - uses: actions/checkout@v4 + with: + token: ${{ secrets.BENCH_BOT_TOKEN || github.token }} - name: Configure run: | @@ -139,28 +140,14 @@ jobs: --prefix latest \ --title "Patternia vs Standard C++" - - name: Create benchmark chart PR - uses: peter-evans/create-pull-request@v8 - with: - add-paths: | - docs/assets/bench/latest.png - docs/assets/bench/latest.md - docs/assets/bench/latest.csv - branch: chore/update-benchmark-summary - commit-message: "ci: update benchmark summary chart" - title: "ci: update benchmark summary chart" - draft: always-true - body: | - ## Summary - - Updates generated benchmark summary assets from the latest `main` benchmark run. - - ## Tests - - - Generated by the benchmark workflow. - - ## Notes - - This PR is created as a draft because GitHub does not trigger required checks - from pull requests created with `GITHUB_TOKEN`. Mark it ready for review to - run the required PR check before merging. + - name: Commit and push chart update + run: | + if git diff --quiet -- docs/assets/bench/; then + echo "No chart changes." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add docs/assets/bench/ + git commit -m "ci: update benchmark summary chart" + git push diff --git a/.github/workflows/clang-format-auto.yml b/.github/workflows/clang-format-auto.yml index 843cc71..13db626 100644 --- a/.github/workflows/clang-format-auto.yml +++ b/.github/workflows/clang-format-auto.yml @@ -2,7 +2,7 @@ name: clang-format Auto Format on: pull_request: - branches: [main, dev-main] + branches: [main] paths: - "**/*.h" - "**/*.hpp" diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index de4c20d..f4316db 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -47,3 +47,5 @@ foreach(_ptn_bench_target IN ITEMS ptn_bench ptn_bench_lit ptn_bench_evolution p endif() endforeach() +add_subdirectory(compile-time) + diff --git a/bench/bench_ptn_lit.cpp b/bench/bench_ptn_lit.cpp index 90a1c3c..52d4c0a 100644 --- a/bench/bench_ptn_lit.cpp +++ b/bench/bench_ptn_lit.cpp @@ -1,14 +1,17 @@ #define PTN_BENCH_MODE PTN_BENCH_MODE_PIPE_STD -#define PTN_BENCH_ENABLE_SUITE_VARIANT 0 -#define PTN_BENCH_ENABLE_SUITE_VARIANT_GUARDED 0 -#define PTN_BENCH_ENABLE_SUITE_VARIANT_FASTPATH 0 -#define PTN_BENCH_ENABLE_SUITE_PROTOCOL_ROUTER 0 -#define PTN_BENCH_ENABLE_SUITE_COMMAND_PARSER 0 -#define PTN_BENCH_ENABLE_SUITE_PACKET 0 +#define PTN_BENCH_ENABLE_SUITE_VARIANT 0 +#define PTN_BENCH_ENABLE_SUITE_VARIANT_GUARDED 0 +#define PTN_BENCH_ENABLE_SUITE_VARIANT_FASTPATH 0 +#define PTN_BENCH_ENABLE_SUITE_PROTOCOL_ROUTER 0 +#define PTN_BENCH_ENABLE_SUITE_COMMAND_PARSER 0 +#define PTN_BENCH_ENABLE_SUITE_PACKET 0 #define PTN_BENCH_ENABLE_SUITE_PACKET_HEAVY_BIND 0 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 1 #define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 1 #include "bench_suite.cpp" diff --git a/bench/bench_suite.cpp b/bench/bench_suite.cpp index 19f9ecb..db2c633 100644 --- a/bench/bench_suite.cpp +++ b/bench/bench_suite.cpp @@ -44,6 +44,18 @@ #define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 1 #endif +#ifndef PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 1 +#endif + +#ifndef PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 1 +#endif + +#ifndef PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 1 +#endif + #ifndef PTN_BENCH_ENABLE_SUITE_PACKET #define PTN_BENCH_ENABLE_SUITE_PACKET 1 #endif @@ -178,9 +190,7 @@ namespace { using ptn::pat::is; return match(v) - | on(is() >> 1, - is() >> 2, - __ >> 0); + | on(is() >> 1, is() >> 2, __ >> 0); } static int patternia_pipe_variant_route(const V &v) { @@ -805,6 +815,136 @@ namespace { } } + // ---------- 16 / 32 / 64 case literal match macros + // ---------------- Runtime-literal variants (lit(n) >> n) for P1-C + // tier benchmarking. Kept separate from the existing val + // (static literal) macros. + +#define PTN_RT_LIT_CASE(n) ptn::lit(n) >> (n) + +#define PTN_RT_LIT_BLOCK_4(base) \ + PTN_RT_LIT_CASE((base) + 0), PTN_RT_LIT_CASE((base) + 1), \ + PTN_RT_LIT_CASE((base) + 2), PTN_RT_LIT_CASE((base) + 3) + +#define PTN_RT_LIT_BLOCK_8(base) \ + PTN_RT_LIT_BLOCK_4((base) + 0), PTN_RT_LIT_BLOCK_4((base) + 4) + +#define PTN_RT_LIT_BLOCK_16(base) \ + PTN_RT_LIT_BLOCK_8((base) + 0), PTN_RT_LIT_BLOCK_8((base) + 8) + +#define PTN_IFELSE_LIT_CASE(n) \ + if (x == (n)) { \ + return (n); \ + } + +#define PTN_IFELSE_LIT_BLOCK_4(base) \ + PTN_IFELSE_LIT_CASE((base) + 0); \ + PTN_IFELSE_LIT_CASE((base) + 1); \ + PTN_IFELSE_LIT_CASE((base) + 2); \ + PTN_IFELSE_LIT_CASE((base) + 3) + +#define PTN_IFELSE_LIT_BLOCK_8(base) \ + PTN_IFELSE_LIT_BLOCK_4((base) + 0); \ + PTN_IFELSE_LIT_BLOCK_4((base) + 4) + +#define PTN_IFELSE_LIT_BLOCK_16(base) \ + PTN_IFELSE_LIT_BLOCK_8((base) + 0); \ + PTN_IFELSE_LIT_BLOCK_8((base) + 8) + +#define PTN_SWITCH_LIT_CASE(n) \ + case (n): \ + return (n) + +#define PTN_SWITCH_LIT_BLOCK_4(base) \ + PTN_SWITCH_LIT_CASE((base) + 0); \ + PTN_SWITCH_LIT_CASE((base) + 1); \ + PTN_SWITCH_LIT_CASE((base) + 2); \ + PTN_SWITCH_LIT_CASE((base) + 3) + +#define PTN_SWITCH_LIT_BLOCK_8(base) \ + PTN_SWITCH_LIT_BLOCK_4((base) + 0); \ + PTN_SWITCH_LIT_BLOCK_4((base) + 4) + +#define PTN_SWITCH_LIT_BLOCK_16(base) \ + PTN_SWITCH_LIT_BLOCK_8((base) + 0); \ + PTN_SWITCH_LIT_BLOCK_8((base) + 8) + + // ---- 16-case (lit runtime) ---- + + static int patternia_pipe_literal_match_16_route(int x) { + using namespace ptn; + return match(x) | on(PTN_RT_LIT_BLOCK_16(1), __ >> 0); + } + + static int if_else_literal_match_16_route(int x) { + PTN_IFELSE_LIT_BLOCK_16(1); + return 0; + } + + static int switch_literal_match_16_route(int x) { + switch (x) { + PTN_SWITCH_LIT_BLOCK_16(1); + default: + return 0; + } + } + + // ---- 32-case (lit runtime) ---- + + static int patternia_pipe_literal_match_32_route(int x) { + using namespace ptn; + return match(x) + | on(PTN_RT_LIT_BLOCK_16(1), + PTN_RT_LIT_BLOCK_16(17), + __ >> 0); + } + + static int if_else_literal_match_32_route(int x) { + PTN_IFELSE_LIT_BLOCK_16(1); + PTN_IFELSE_LIT_BLOCK_16(17); + return 0; + } + + static int switch_literal_match_32_route(int x) { + switch (x) { + PTN_SWITCH_LIT_BLOCK_16(1); + PTN_SWITCH_LIT_BLOCK_16(17); + default: + return 0; + } + } + + // ---- 64-case (lit runtime) ---- + + static int patternia_pipe_literal_match_64_route(int x) { + using namespace ptn; + return match(x) + | on(PTN_RT_LIT_BLOCK_16(1), + PTN_RT_LIT_BLOCK_16(17), + PTN_RT_LIT_BLOCK_16(33), + PTN_RT_LIT_BLOCK_16(49), + __ >> 0); + } + + static int if_else_literal_match_64_route(int x) { + PTN_IFELSE_LIT_BLOCK_16(1); + PTN_IFELSE_LIT_BLOCK_16(17); + PTN_IFELSE_LIT_BLOCK_16(33); + PTN_IFELSE_LIT_BLOCK_16(49); + return 0; + } + + static int switch_literal_match_64_route(int x) { + switch (x) { + PTN_SWITCH_LIT_BLOCK_16(1); + PTN_SWITCH_LIT_BLOCK_16(17); + PTN_SWITCH_LIT_BLOCK_16(33); + PTN_SWITCH_LIT_BLOCK_16(49); + default: + return 0; + } + } + #define PTN_LIT_CASE_128(n) ptn::val >> (n) #define PTN_SWITCH_CASE_128(n) \ case (n): \ @@ -995,15 +1135,14 @@ namespace { }; return match(pkt) - | on($(has<&Packet::type, - &Packet::length>)[is_ping_packet] - >> 1, - $(has<&Packet::type, - &Packet::length, - &Packet::flags>)[is_valid_data_packet] - >> 2, - $(has<&Packet::type>)[is_error_packet] >> 3, - __ >> 0); + | on( + $(has<&Packet::type, &Packet::length>)[is_ping_packet] + >> 1, + $(has<&Packet::type, &Packet::length, &Packet::flags>) + [is_valid_data_packet] + >> 2, + $(has<&Packet::type>)[is_error_packet] >> 3, + __ >> 0); } static int patternia_pipe_packet_route(const Packet &pkt) { @@ -1026,15 +1165,14 @@ namespace { }; return match(pkt) - | on($(has<&Packet::type, - &Packet::length>)[is_ping_packet] - >> 1, - $(has<&Packet::type, - &Packet::length, - &Packet::flags>)[is_valid_data_packet] - >> 2, - $(has<&Packet::type>)[is_error_packet] >> 3, - __ >> 0); + | on( + $(has<&Packet::type, &Packet::length>)[is_ping_packet] + >> 1, + $(has<&Packet::type, &Packet::length, &Packet::flags>) + [is_valid_data_packet] + >> 2, + $(has<&Packet::type>)[is_error_packet] >> 3, + __ >> 0); } static int switch_packet_route(const Packet &pkt) { @@ -1086,18 +1224,18 @@ namespace { }; return match(pkt) - | on($(has<&Packet::type, - &Packet::length>)[is_ping_packet] - >> 1, - $(has<&Packet::type, - &Packet::length, - &Packet::flags, - &Packet::payload>)[is_valid_data_packet] - >> 2, - $(has<&Packet::type, - &Packet::payload>)[is_error_packet] - >> 3, - __ >> 0); + | on( + $(has<&Packet::type, &Packet::length>)[is_ping_packet] + >> 1, + $(has<&Packet::type, + &Packet::length, + &Packet::flags, + &Packet::payload>)[is_valid_data_packet] + >> 2, + $(has<&Packet::type, + &Packet::payload>)[is_error_packet] + >> 3, + __ >> 0); } static int @@ -1126,18 +1264,18 @@ namespace { }; return match(pkt) - | on($(has<&Packet::type, - &Packet::length>)[is_ping_packet] - >> 1, - $(has<&Packet::type, - &Packet::length, - &Packet::flags, - &Packet::payload>)[is_valid_data_packet] - >> 2, - $(has<&Packet::type, - &Packet::payload>)[is_error_packet] - >> 3, - __ >> 0); + | on( + $(has<&Packet::type, &Packet::length>)[is_ping_packet] + >> 1, + $(has<&Packet::type, + &Packet::length, + &Packet::flags, + &Packet::payload>)[is_valid_data_packet] + >> 2, + $(has<&Packet::type, + &Packet::payload>)[is_error_packet] + >> 3, + __ >> 0); } static int switch_packet_heavy_bind_route(const Packet &pkt) { @@ -1353,6 +1491,30 @@ namespace { return data; } + template + static const std::vector &literal_n_workload() { + static const std::vector data = [] { + std::vector v; + v.reserve(static_cast(N * 2 + 4)); + for (int i = 1; i <= N; ++i) { + v.push_back(i); + } + v.push_back(0); + v.push_back(N + 1); + v.push_back(999); + for (int i = N; i >= 1; --i) { + v.push_back(i); + } + return v; + }(); + return data; + } + + template <> + const std::vector &literal_n_workload<8>() { + return literal_workload(); + } + template static void run_workload(benchmark::State &state, const std::vector &workload, @@ -1651,6 +1813,63 @@ namespace { state, literal_workload(), switch_literal_match_route); } + static void + BM_PatterniaPipe_LiteralMatch16(benchmark::State &state) { + run_workload(state, + literal_n_workload<16>(), + patternia_pipe_literal_match_16_route); + } + + static void BM_IfElse_LiteralMatch16(benchmark::State &state) { + run_workload(state, + literal_n_workload<16>(), + if_else_literal_match_16_route); + } + + static void BM_Switch_LiteralMatch16(benchmark::State &state) { + run_workload(state, + literal_n_workload<16>(), + switch_literal_match_16_route); + } + + static void + BM_PatterniaPipe_LiteralMatch32(benchmark::State &state) { + run_workload(state, + literal_n_workload<32>(), + patternia_pipe_literal_match_32_route); + } + + static void BM_IfElse_LiteralMatch32(benchmark::State &state) { + run_workload(state, + literal_n_workload<32>(), + if_else_literal_match_32_route); + } + + static void BM_Switch_LiteralMatch32(benchmark::State &state) { + run_workload(state, + literal_n_workload<32>(), + switch_literal_match_32_route); + } + + static void + BM_PatterniaPipe_LiteralMatch64(benchmark::State &state) { + run_workload(state, + literal_n_workload<64>(), + patternia_pipe_literal_match_64_route); + } + + static void BM_IfElse_LiteralMatch64(benchmark::State &state) { + run_workload(state, + literal_n_workload<64>(), + if_else_literal_match_64_route); + } + + static void BM_Switch_LiteralMatch64(benchmark::State &state) { + run_workload(state, + literal_n_workload<64>(), + switch_literal_match_64_route); + } + static void BM_PatterniaPipe_LiteralMatch128StaticCases( benchmark::State &state) { run_workload( @@ -1746,6 +1965,24 @@ PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch); PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch); #endif +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch16); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch16); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch16); +#endif + +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch32); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch32); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch32); +#endif + +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch64); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch64); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch64); +#endif + #if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 PTN_REGISTER_STABLE_BENCH( BM_PatterniaPipe_LiteralMatch128StaticCases); @@ -1808,6 +2045,24 @@ PTN_REGISTER_STABLE_BENCH(BM_Patternia_LiteralMatch); PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch); #endif +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch16); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch16); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch16); +#endif + +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch32); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch32); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch32); +#endif + +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatch64); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch64); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch64); +#endif + #if PTN_BENCH_ENABLE_SUITE_PACKET PTN_REGISTER_STABLE_BENCH(BM_Patternia_PacketMixed); PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_PacketMixed); @@ -1827,3 +2082,15 @@ PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_PacketMixedHeavyBind); #undef PTN_LIT_BLOCK_16 #undef PTN_SWITCH_CASE_128 #undef PTN_LIT_CASE_128 +#undef PTN_RT_LIT_CASE +#undef PTN_RT_LIT_BLOCK_4 +#undef PTN_RT_LIT_BLOCK_8 +#undef PTN_RT_LIT_BLOCK_16 +#undef PTN_IFELSE_LIT_CASE +#undef PTN_IFELSE_LIT_BLOCK_4 +#undef PTN_IFELSE_LIT_BLOCK_8 +#undef PTN_IFELSE_LIT_BLOCK_16 +#undef PTN_SWITCH_LIT_CASE +#undef PTN_SWITCH_LIT_BLOCK_4 +#undef PTN_SWITCH_LIT_BLOCK_8 +#undef PTN_SWITCH_LIT_BLOCK_16 diff --git a/bench/compile-time/CMakeLists.txt b/bench/compile-time/CMakeLists.txt new file mode 100644 index 0000000..ee1da03 --- /dev/null +++ b/bench/compile-time/CMakeLists.txt @@ -0,0 +1,93 @@ +include(FetchContent) +find_package(Threads REQUIRED) + +# ── literal-match compile-time TU helper ────────────────────── +# Each TU instantiates match(...) | on( lit(0)>>0, lit(1)>>1, ... ) +# at compile time (in a constexpr function + static_assert chain), +# so the TU itself exercises the template machinery without any +# runtime dependency on Google Benchmark. + +function(_ptn_ct_add_lit_tu N) + set(_tufile "${CMAKE_CURRENT_SOURCE_DIR}/ct_bench_lit_${N}.cpp") + file(WRITE "${_tufile}" + "// compile-time literal-match benchmark – forces full instantiation\n" + "// of the match-dispatch machinery with ${N} literal cases.\n\n" + "#include \"ptn/patternia.hpp\"\n\n" + ) + + # constexpr function body + file(APPEND "${_tufile}" + "constexpr int ct_lit_${N}(int v) noexcept {\n" + " using namespace ptn;\n" + " return match(v) | on(\n" + ) + set(_cases "") + math(EXPR _last "${N} - 1") + foreach(_i RANGE 0 ${_last} 1) + string(APPEND _cases " ptn::lit(${_i}) >> ${_i},\n") + endforeach() + string(APPEND _cases " __ >> 0);\n") + file(APPEND "${_tufile}" "${_cases}") + file(APPEND "${_tufile}" "}\n\n") + + # static_assert chain to force constexpr evaluation + math(EXPR _half "${N} / 2") + file(APPEND "${_tufile}" + "static_assert(ct_lit_${N}(0) == 0, \"\");\n" + "static_assert(ct_lit_${N}(${_half}) == ${_half}, \"\");\n" + "static_assert(ct_lit_${N}(${_last}) == ${_last}, \"\");\n" + "static_assert(ct_lit_${N}(-1) == 0, \"\");\n" + "static_assert(ct_lit_${N}(${N}) == 0, \"\");\n" + ) + + # object library – compile-only, no link + add_library(ptn_ct_lit_${N} OBJECT "${_tufile}") + target_link_libraries(ptn_ct_lit_${N} PRIVATE patternia Threads::Threads) + target_compile_features(ptn_ct_lit_${N} PRIVATE cxx_std_17) + + if(MSVC) + target_compile_options(ptn_ct_lit_${N} PRIVATE /O2) + else() + target_compile_options(ptn_ct_lit_${N} PRIVATE -O3) + endif() +endfunction() + +# ── generate literal-match TUs for 8/16/32/64/128 ──────────── +_ptn_ct_add_lit_tu(8) +_ptn_ct_add_lit_tu(16) +_ptn_ct_add_lit_tu(32) +_ptn_ct_add_lit_tu(64) +_ptn_ct_add_lit_tu(128) + +# ── variant compile-time bench ──────────────────────────────── +# Forces instantiation of std::variant alt-matching with 32 alts. +add_library(ptn_ct_variant_32 OBJECT ct_bench_variant_32.cpp) +target_link_libraries(ptn_ct_variant_32 PRIVATE patternia Threads::Threads) +target_compile_features(ptn_ct_variant_32 PRIVATE cxx_std_17) +if(MSVC) + target_compile_options(ptn_ct_variant_32 PRIVATE /O2) +else() + target_compile_options(ptn_ct_variant_32 PRIVATE -O3) +endif() + +# ── compound compile-time bench ─────────────────────────────── +# Combines multiple pattern types (literal, variant, predicate) +# in a single TU to measure worst-case instantiation cost. +add_library(ptn_ct_compound OBJECT ct_bench_compound.cpp) +target_link_libraries(ptn_ct_compound PRIVATE patternia Threads::Threads) +target_compile_features(ptn_ct_compound PRIVATE cxx_std_17) +if(MSVC) + target_compile_options(ptn_ct_compound PRIVATE /O2) +else() + target_compile_options(ptn_ct_compound PRIVATE -O3) +endif() + +# ── aggregate convenience target ────────────────────────────── +# `cmake --build build --target ptn_bench_ct` compiles every TU. +add_custom_target(ptn_bench_ct) +add_dependencies(ptn_bench_ct + ptn_ct_lit_8 ptn_ct_lit_16 ptn_ct_lit_32 + ptn_ct_lit_64 ptn_ct_lit_128 + ptn_ct_variant_32 + ptn_ct_compound +) diff --git a/bench/compile-time/ct_bench_compound.cpp b/bench/compile-time/ct_bench_compound.cpp new file mode 100644 index 0000000..6512fdc --- /dev/null +++ b/bench/compile-time/ct_bench_compound.cpp @@ -0,0 +1,28 @@ +#include "ptn/patternia.hpp" + +#include +#include + +namespace { + using VariantT = std::variant; + + int ct_combo_lit(int x) { + using namespace ptn; + return match(x) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + __ >> 0); + } + + int ct_combo_variant(const VariantT &v) { + using namespace ptn; + return match(v) + | on(is() >> 100, is() >> 200, __ >> 0); + } + + int (*volatile sink_lit)(int) = ct_combo_lit; + int (*volatile sink_var)(const VariantT &) = ct_combo_variant; +} // namespace diff --git a/bench/compile-time/ct_bench_lit_128.cpp b/bench/compile-time/ct_bench_lit_128.cpp new file mode 100644 index 0000000..c7754b9 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_128.cpp @@ -0,0 +1,144 @@ +// compile-time literal-match benchmark – forces full instantiation +// of the match-dispatch machinery with 128 literal cases. + +#include "ptn/patternia.hpp" + +constexpr int ct_lit_128(int v) noexcept { + using namespace ptn; + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + ptn::lit(64) >> 64, + ptn::lit(65) >> 65, + ptn::lit(66) >> 66, + ptn::lit(67) >> 67, + ptn::lit(68) >> 68, + ptn::lit(69) >> 69, + ptn::lit(70) >> 70, + ptn::lit(71) >> 71, + ptn::lit(72) >> 72, + ptn::lit(73) >> 73, + ptn::lit(74) >> 74, + ptn::lit(75) >> 75, + ptn::lit(76) >> 76, + ptn::lit(77) >> 77, + ptn::lit(78) >> 78, + ptn::lit(79) >> 79, + ptn::lit(80) >> 80, + ptn::lit(81) >> 81, + ptn::lit(82) >> 82, + ptn::lit(83) >> 83, + ptn::lit(84) >> 84, + ptn::lit(85) >> 85, + ptn::lit(86) >> 86, + ptn::lit(87) >> 87, + ptn::lit(88) >> 88, + ptn::lit(89) >> 89, + ptn::lit(90) >> 90, + ptn::lit(91) >> 91, + ptn::lit(92) >> 92, + ptn::lit(93) >> 93, + ptn::lit(94) >> 94, + ptn::lit(95) >> 95, + ptn::lit(96) >> 96, + ptn::lit(97) >> 97, + ptn::lit(98) >> 98, + ptn::lit(99) >> 99, + ptn::lit(100) >> 100, + ptn::lit(101) >> 101, + ptn::lit(102) >> 102, + ptn::lit(103) >> 103, + ptn::lit(104) >> 104, + ptn::lit(105) >> 105, + ptn::lit(106) >> 106, + ptn::lit(107) >> 107, + ptn::lit(108) >> 108, + ptn::lit(109) >> 109, + ptn::lit(110) >> 110, + ptn::lit(111) >> 111, + ptn::lit(112) >> 112, + ptn::lit(113) >> 113, + ptn::lit(114) >> 114, + ptn::lit(115) >> 115, + ptn::lit(116) >> 116, + ptn::lit(117) >> 117, + ptn::lit(118) >> 118, + ptn::lit(119) >> 119, + ptn::lit(120) >> 120, + ptn::lit(121) >> 121, + ptn::lit(122) >> 122, + ptn::lit(123) >> 123, + ptn::lit(124) >> 124, + ptn::lit(125) >> 125, + ptn::lit(126) >> 126, + ptn::lit(127) >> 127, + __ >> 0); +} + +static_assert(ct_lit_128(0) == 0, ""); +static_assert(ct_lit_128(64) == 64, ""); +static_assert(ct_lit_128(127) == 127, ""); +static_assert(ct_lit_128(-1) == 0, ""); +static_assert(ct_lit_128(128) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_16.cpp b/bench/compile-time/ct_bench_lit_16.cpp new file mode 100644 index 0000000..36b9d69 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_16.cpp @@ -0,0 +1,32 @@ +// compile-time literal-match benchmark – forces full instantiation +// of the match-dispatch machinery with 16 literal cases. + +#include "ptn/patternia.hpp" + +constexpr int ct_lit_16(int v) noexcept { + using namespace ptn; + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + __ >> 0); +} + +static_assert(ct_lit_16(0) == 0, ""); +static_assert(ct_lit_16(8) == 8, ""); +static_assert(ct_lit_16(15) == 15, ""); +static_assert(ct_lit_16(-1) == 0, ""); +static_assert(ct_lit_16(16) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_32.cpp b/bench/compile-time/ct_bench_lit_32.cpp new file mode 100644 index 0000000..3221391 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_32.cpp @@ -0,0 +1,48 @@ +// compile-time literal-match benchmark – forces full instantiation +// of the match-dispatch machinery with 32 literal cases. + +#include "ptn/patternia.hpp" + +constexpr int ct_lit_32(int v) noexcept { + using namespace ptn; + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + __ >> 0); +} + +static_assert(ct_lit_32(0) == 0, ""); +static_assert(ct_lit_32(16) == 16, ""); +static_assert(ct_lit_32(31) == 31, ""); +static_assert(ct_lit_32(-1) == 0, ""); +static_assert(ct_lit_32(32) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_64.cpp b/bench/compile-time/ct_bench_lit_64.cpp new file mode 100644 index 0000000..fe78b18 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_64.cpp @@ -0,0 +1,80 @@ +// compile-time literal-match benchmark – forces full instantiation +// of the match-dispatch machinery with 64 literal cases. + +#include "ptn/patternia.hpp" + +constexpr int ct_lit_64(int v) noexcept { + using namespace ptn; + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + __ >> 0); +} + +static_assert(ct_lit_64(0) == 0, ""); +static_assert(ct_lit_64(32) == 32, ""); +static_assert(ct_lit_64(63) == 63, ""); +static_assert(ct_lit_64(-1) == 0, ""); +static_assert(ct_lit_64(64) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_8.cpp b/bench/compile-time/ct_bench_lit_8.cpp new file mode 100644 index 0000000..752afae --- /dev/null +++ b/bench/compile-time/ct_bench_lit_8.cpp @@ -0,0 +1,24 @@ +// compile-time literal-match benchmark – forces full instantiation +// of the match-dispatch machinery with 8 literal cases. + +#include "ptn/patternia.hpp" + +constexpr int ct_lit_8(int v) noexcept { + using namespace ptn; + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + __ >> 0); +} + +static_assert(ct_lit_8(0) == 0, ""); +static_assert(ct_lit_8(4) == 4, ""); +static_assert(ct_lit_8(7) == 7, ""); +static_assert(ct_lit_8(-1) == 0, ""); +static_assert(ct_lit_8(8) == 0, ""); diff --git a/bench/compile-time/ct_bench_variant_32.cpp b/bench/compile-time/ct_bench_variant_32.cpp new file mode 100644 index 0000000..ae7382d --- /dev/null +++ b/bench/compile-time/ct_bench_variant_32.cpp @@ -0,0 +1,84 @@ +#include "ptn/patternia.hpp" + +#include +#include + +namespace { + template + struct Token { + int value; + }; + + using VAlt32 = std::variant, + Token<1>, + Token<2>, + Token<3>, + Token<4>, + Token<5>, + Token<6>, + Token<7>, + Token<8>, + Token<9>, + Token<10>, + Token<11>, + Token<12>, + Token<13>, + Token<14>, + Token<15>, + Token<16>, + Token<17>, + Token<18>, + Token<19>, + Token<20>, + Token<21>, + Token<22>, + Token<23>, + Token<24>, + Token<25>, + Token<26>, + Token<27>, + Token<28>, + Token<29>, + Token<30>, + Token<31>>; + + int ct_var_alt_route(const VAlt32 &v) { + using namespace ptn; + return match(v) + | on(alt<0>() >> 1, + alt<1>() >> 2, + alt<2>() >> 3, + alt<3>() >> 4, + alt<4>() >> 5, + alt<5>() >> 6, + alt<6>() >> 7, + alt<7>() >> 8, + alt<8>() >> 9, + alt<9>() >> 10, + alt<10>() >> 11, + alt<11>() >> 12, + alt<12>() >> 13, + alt<13>() >> 14, + alt<14>() >> 15, + alt<15>() >> 16, + alt<16>() >> 17, + alt<17>() >> 18, + alt<18>() >> 19, + alt<19>() >> 20, + alt<20>() >> 21, + alt<21>() >> 22, + alt<22>() >> 23, + alt<23>() >> 24, + alt<24>() >> 25, + alt<25>() >> 26, + alt<26>() >> 27, + alt<27>() >> 28, + alt<28>() >> 29, + alt<29>() >> 30, + alt<30>() >> 31, + alt<31>() >> 32, + __ >> 0); + } + + int (*volatile sink)(const VAlt32 &) = ct_var_alt_route; +} // namespace diff --git a/packaging/vcpkg/portfile.cmake b/packaging/vcpkg/portfile.cmake index b6e5eff..46c7616 100644 --- a/packaging/vcpkg/portfile.cmake +++ b/packaging/vcpkg/portfile.cmake @@ -4,7 +4,7 @@ vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO SentoMK/patternia REF "v${VERSION}" - SHA512 482f20b7664ff4cb3931e95c57e582f615063e092c734b6d02c32a97f56250dd8fd41611f2773c6d2c8d10fd924fb9700dcbdbeaa222f98ba2e35638733ed736 + SHA512 727fbde75b30e38b4a813b966115b7a2e142617a8a8143bb88eb7dbbb6ce99979a25d4ee288aac5eafe9d48798a6dc6936b58804882b22acfa60b1a609a0ae53 HEAD_REF main ) diff --git a/scripts/bench_ct_compare.py b/scripts/bench_ct_compare.py new file mode 100644 index 0000000..18fdb7b --- /dev/null +++ b/scripts/bench_ct_compare.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +"""Measure compile-time for patternia compile-time benchmarks. + +Compiles each compile-time TU N times and reports median wall-clock +compilation time. Used as a regression gate: if the median compile +time of any TU increases beyond a threshold the script exits non-zero. +""" + +from __future__ import annotations + +import argparse +import json +import os +import statistics +import subprocess +import sys +import time +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, List, Optional, Tuple + +REPO_ROOT = Path(__file__).resolve().parents[1] +DEFAULT_BUILD_DIR = REPO_ROOT / "build_ct_bench" + + +@dataclass +class CTResult: + target: str + elapsed_sec: float + + +def _parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser( + description="compile-time benchmark measurement" + ) + p.add_argument( + "--build-dir", default=str(DEFAULT_BUILD_DIR), + help="CMake build directory for compile-time benchmarks" + ) + p.add_argument( + "--target", default="ptn_bench_ct", + help="CMake target to build (aggregate)" + ) + p.add_argument( + "--repeat", type=int, default=3, + help="Number of timed builds per TU (median taken)" + ) + p.add_argument( + "--source-dir", default=str(REPO_ROOT), + help="Source tree root" + ) + p.add_argument( + "--json-out", default=None, + help="Write JSON report to this path" + ) + p.add_argument( + "--baseline-json", default=None, + help="Baseline JSON report for comparison" + ) + p.add_argument( + "--fail-if-regress-pct", type=float, default=5.0, + help="Fail if any TU regresses above this percent" + ) + p.add_argument( + "--fail-if-mean-regress-pct", type=float, default=3.0, + help="Fail if mean regression exceeds this percent" + ) + return p.parse_args() + + +def _configure(build_dir: str, source_dir: str) -> None: + if not Path(build_dir, "CMakeCache.txt").exists(): + subprocess.run( + [ + "cmake", "-S", source_dir, "-B", build_dir, + "-DCMAKE_BUILD_TYPE=Release", + "-DPTN_BUILD_TESTS=OFF", + "-DPTN_BUILD_BENCHMARKS=ON", + "-DPTN_SKIP_COMPILER_CHECK=ON", + ], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + +def _full_clean(build_dir: str) -> None: + """Remove all object files so each run is a full recompile.""" + subprocess.run( + ["cmake", "--build", build_dir, "--target", "clean"], + capture_output=True, + ) + + +def _timed_build(build_dir: str, target: str) -> float: + """Run a single cmake --build and return elapsed wall seconds.""" + start = time.perf_counter() + subprocess.run( + ["cmake", "--build", build_dir, "--target", target, "--parallel"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return time.perf_counter() - start + + +def _measure_tu(build_dir: str, tu_target: str, + repeat: int) -> List[float]: + times: List[float] = [] + for _ in range(repeat): + _full_clean(build_dir) + times.append(_timed_build(build_dir, tu_target)) + return times + + +def _tu_to_obj_target(tu: str) -> str: + """Convert aggregate target name to the underlying object library name.""" + return tu.replace("ptn_bench_ct_", "ptn_ct_") + + +def _list_tu_targets(build_dir: str) -> List[str]: + """Find all ptn_ct_* object targets from the build system.""" + import re + result = subprocess.run( + ["cmake", "--build", build_dir, "--target", "help"], + capture_output=True, + text=True, + check=True, + ) + targets: List[str] = [] + for line in result.stdout.splitlines(): + m = re.match(r"^\s*\.\.\.\s*(ptn_ct_\S+)\s*$", line) + if m: + targets.append(m.group(1)) + return sorted(targets) + + +def _median(lst: List[float]) -> float: + return statistics.median(lst) + + +def main() -> int: + args = _parse_args() + + build_dir = args.build_dir + source_dir = args.source_dir + + _configure(build_dir, source_dir) + + tu_targets = _list_tu_targets(build_dir) + if not tu_targets: + print("ERROR: no compile-time TU targets found", file=sys.stderr) + return 2 + + print(f"Found {len(tu_targets)} compile-time TU targets") + + results: Dict[str, Dict] = {} + + for target in tu_targets: + print(f" measuring {target} ...", end=" ", flush=True) + times = _measure_tu(build_dir, target, args.repeat) + med = _median(times) + results[target] = { + "median_sec": round(med, 3), + "raw_sec": [round(t, 3) for t in times], + } + print(f"{med:.3f}s") + + report = { + "targets": results, + "build_dir": build_dir, + "repeat": args.repeat, + } + + if args.json_out: + with open(args.json_out, "w", encoding="utf-8") as f: + json.dump(report, f, indent=2) + print(f"\nReport written to {args.json_out}") + + if args.baseline_json: + with open(args.baseline_json, "r", encoding="utf-8") as f: + baseline = json.load(f) + baseline_targets = baseline.get("targets", {}) + + print("\nCompile-time regression gate:") + print(f"{'TU':<30} {'base(s)':>10} {'curr(s)':>10} {'delta%':>8}") + print("-" * 60) + + deltas: List[float] = [] + failures = 0 + + for target in sorted(results.keys()): + cur = results[target]["median_sec"] + base_data = baseline_targets.get(target) + if base_data is None: + print(f"{target:<30} {'--':>10} {cur:>10.3f} (new)") + continue + + base = base_data["median_sec"] + delta = (cur - base) / base * 100.0 if base > 0 else 0.0 + deltas.append(delta) + status = "FAIL" if delta > args.fail_if_regress_pct else "OK" + if delta > args.fail_if_regress_pct: + failures += 1 + + print( + f"{target:<30} {base:>10.3f} {cur:>10.3f} {delta:>+7.1f}% {status}" + ) + + if deltas: + mean_delta = statistics.mean(deltas) + print(f"\nMean regression: {mean_delta:+.1f}%") + + if mean_delta > args.fail_if_mean_regress_pct: + print( + f"FAIL: mean regression {mean_delta:+.1f}% exceeds " + f"threshold {args.fail_if_mean_regress_pct}%", + file=sys.stderr, + ) + return 1 + + if failures > 0: + print( + f"FAIL: {failures} TU(s) regressed above " + f"{args.fail_if_regress_pct}%", + file=sys.stderr, + ) + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8a35432e6c358c95b77b5c98c78689961a436306 Mon Sep 17 00:00:00 2001 From: sentomk Date: Fri, 1 May 2026 00:55:12 +0800 Subject: [PATCH 2/3] feat(optimize): add literal_runtime_dense dispatch tier Problem - Literal patterns with density below 1/4 fell back to O(n) linear scan. - An intermediate tier with relaxed density (1/8) can provide O(1) array dispatch for patterns like 10 cases spanning 0-100. Implementation - Added k_runtime_literal_dense_dispatch_max_cases=256 / _max_span=512 / _max_density=8 / _min_cases=4 constants. - Added is_runtime_literal_dense_dispatch_enabled trait. - Added use_runtime_dense_table field to static_literal_dispatch_metadata. - Added can_use_runtime_literal_dense_dispatch to case_sequence_ir and lowering_analysis. - New literal_runtime_dense_dispatch_plan struct and dispatch_plan_for_kind specialization. - Updated dispatch_plan_selector priority: static_dense -> runtime_dense -> literal_linear. - Extended eval_cases_with_dispatch_plan to route runtime_dense plans to the same eval_cases_impl_static_literal_dispatch evaluator. Tests - Added runtime bench suite: 15 sparse literal values (density ~0.15) comparing PatterniaPipe / IfElse / Switch. - Added compile-time TU ct_bench_lit_rdense.cpp exercising the tier at compile time via constexpr + static_assert. Notes - No DSL or public API surface changes. --- bench/bench_ptn_lit.cpp | 11 +- bench/bench_suite.cpp | 105 +++++++++ bench/compile-time/CMakeLists.txt | 13 ++ bench/compile-time/ct_bench_lit_128.cpp | 260 ++++++++++----------- bench/compile-time/ct_bench_lit_16.cpp | 36 +-- bench/compile-time/ct_bench_lit_32.cpp | 68 +++--- bench/compile-time/ct_bench_lit_64.cpp | 132 +++++------ bench/compile-time/ct_bench_lit_8.cpp | 20 +- bench/compile-time/ct_bench_lit_rdense.cpp | 24 ++ include/ptn/core/common/eval.hpp | 4 +- include/ptn/core/common/optimize.hpp | 107 ++++++++- 11 files changed, 508 insertions(+), 272 deletions(-) create mode 100644 bench/compile-time/ct_bench_lit_rdense.cpp diff --git a/bench/bench_ptn_lit.cpp b/bench/bench_ptn_lit.cpp index 52d4c0a..08b8913 100644 --- a/bench/bench_ptn_lit.cpp +++ b/bench/bench_ptn_lit.cpp @@ -8,10 +8,11 @@ #define PTN_BENCH_ENABLE_SUITE_PACKET 0 #define PTN_BENCH_ENABLE_SUITE_PACKET_HEAVY_BIND 0 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH 1 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 1 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 1 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 1 -#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_16 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_32 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 1 +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_RDENSE 1 #include "bench_suite.cpp" diff --git a/bench/bench_suite.cpp b/bench/bench_suite.cpp index db2c633..36991fa 100644 --- a/bench/bench_suite.cpp +++ b/bench/bench_suite.cpp @@ -56,6 +56,10 @@ #define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_64 1 #endif +#ifndef PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_RDENSE +#define PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_RDENSE 1 +#endif + #ifndef PTN_BENCH_ENABLE_SUITE_PACKET #define PTN_BENCH_ENABLE_SUITE_PACKET 1 #endif @@ -945,6 +949,64 @@ namespace { } } + // 15 literal values spanning [0, 98] → density ≈ 0.15 + // (between 1/8 and 1/4), designed to exercise the + // literal_runtime_dense dispatch tier. + + static int patternia_pipe_literal_match_rdense_route(int x) { + using namespace ptn; + return match(x) + | on(ptn::lit(0) >> 0, ptn::lit(7) >> 7, + ptn::lit(14) >> 14, ptn::lit(21) >> 21, + ptn::lit(28) >> 28, ptn::lit(35) >> 35, + ptn::lit(42) >> 42, ptn::lit(49) >> 49, + ptn::lit(56) >> 56, ptn::lit(63) >> 63, + ptn::lit(70) >> 70, ptn::lit(77) >> 77, + ptn::lit(84) >> 84, ptn::lit(91) >> 91, + ptn::lit(98) >> 98, + __ >> 0); + } + + static int if_else_literal_match_rdense_route(int x) { + if (x == 0) return 0; + if (x == 7) return 7; + if (x == 14) return 14; + if (x == 21) return 21; + if (x == 28) return 28; + if (x == 35) return 35; + if (x == 42) return 42; + if (x == 49) return 49; + if (x == 56) return 56; + if (x == 63) return 63; + if (x == 70) return 70; + if (x == 77) return 77; + if (x == 84) return 84; + if (x == 91) return 91; + if (x == 98) return 98; + return 0; + } + + static int switch_literal_match_rdense_route(int x) { + switch (x) { + case 0: return 0; + case 7: return 7; + case 14: return 14; + case 21: return 21; + case 28: return 28; + case 35: return 35; + case 42: return 42; + case 49: return 49; + case 56: return 56; + case 63: return 63; + case 70: return 70; + case 77: return 77; + case 84: return 84; + case 91: return 91; + case 98: return 98; + default: return 0; + } + } + #define PTN_LIT_CASE_128(n) ptn::val >> (n) #define PTN_SWITCH_CASE_128(n) \ case (n): \ @@ -1515,6 +1577,18 @@ namespace { return literal_workload(); } + static const std::vector &literal_rdense_workload() { + static const std::vector data = { + 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, + 70, 77, 84, 91, 98, // all 15 sparse values + 3, 10, 17, 31, 55, 99, // misses + -1, 100, // out-of-range + 98, 91, 84, 77, 70, 63, 56, 49, 42, 35, + 28, 21, 14, 7, 0, // reverse hits + }; + return data; + } + template static void run_workload(benchmark::State &state, const std::vector &workload, @@ -1898,6 +1972,25 @@ namespace { switch_literal_match_128_route); } + static void + BM_PatterniaPipe_LiteralMatchRDense(benchmark::State &state) { + run_workload(state, + literal_rdense_workload(), + patternia_pipe_literal_match_rdense_route); + } + + static void BM_IfElse_LiteralMatchRDense(benchmark::State &state) { + run_workload(state, + literal_rdense_workload(), + if_else_literal_match_rdense_route); + } + + static void BM_Switch_LiteralMatchRDense(benchmark::State &state) { + run_workload(state, + literal_rdense_workload(), + switch_literal_match_rdense_route); + } + } // namespace #define PTN_REGISTER_STABLE_BENCH(name) \ @@ -1983,6 +2076,12 @@ PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch64); PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch64); #endif +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_RDENSE +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatchRDense); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatchRDense); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatchRDense); +#endif + #if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_128 PTN_REGISTER_STABLE_BENCH( BM_PatterniaPipe_LiteralMatch128StaticCases); @@ -2063,6 +2162,12 @@ PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatch64); PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatch64); #endif +#if PTN_BENCH_ENABLE_SUITE_LITERAL_MATCH_RDENSE +PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_LiteralMatchRDense); +PTN_REGISTER_STABLE_BENCH(BM_IfElse_LiteralMatchRDense); +PTN_REGISTER_STABLE_BENCH(BM_Switch_LiteralMatchRDense); +#endif + #if PTN_BENCH_ENABLE_SUITE_PACKET PTN_REGISTER_STABLE_BENCH(BM_Patternia_PacketMixed); PTN_REGISTER_STABLE_BENCH(BM_PatterniaPipe_PacketMixed); diff --git a/bench/compile-time/CMakeLists.txt b/bench/compile-time/CMakeLists.txt index ee1da03..7b91204 100644 --- a/bench/compile-time/CMakeLists.txt +++ b/bench/compile-time/CMakeLists.txt @@ -82,6 +82,18 @@ else() target_compile_options(ptn_ct_compound PRIVATE -O3) endif() +# ── runtime-dense compile-time bench ─────────────────────────── +# 15 sparse literal values (density ≈ 0.15) to exercise the +# literal_runtime_dense dispatch tier at compile time. +add_library(ptn_ct_lit_rdense OBJECT ct_bench_lit_rdense.cpp) +target_link_libraries(ptn_ct_lit_rdense PRIVATE patternia Threads::Threads) +target_compile_features(ptn_ct_lit_rdense PRIVATE cxx_std_17) +if(MSVC) + target_compile_options(ptn_ct_lit_rdense PRIVATE /O2) +else() + target_compile_options(ptn_ct_lit_rdense PRIVATE -O3) +endif() + # ── aggregate convenience target ────────────────────────────── # `cmake --build build --target ptn_bench_ct` compiles every TU. add_custom_target(ptn_bench_ct) @@ -90,4 +102,5 @@ add_dependencies(ptn_bench_ct ptn_ct_lit_64 ptn_ct_lit_128 ptn_ct_variant_32 ptn_ct_compound + ptn_ct_lit_rdense ) diff --git a/bench/compile-time/ct_bench_lit_128.cpp b/bench/compile-time/ct_bench_lit_128.cpp index c7754b9..3a05a94 100644 --- a/bench/compile-time/ct_bench_lit_128.cpp +++ b/bench/compile-time/ct_bench_lit_128.cpp @@ -5,136 +5,136 @@ constexpr int ct_lit_128(int v) noexcept { using namespace ptn; - return match(v) - | on(ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - ptn::lit(32) >> 32, - ptn::lit(33) >> 33, - ptn::lit(34) >> 34, - ptn::lit(35) >> 35, - ptn::lit(36) >> 36, - ptn::lit(37) >> 37, - ptn::lit(38) >> 38, - ptn::lit(39) >> 39, - ptn::lit(40) >> 40, - ptn::lit(41) >> 41, - ptn::lit(42) >> 42, - ptn::lit(43) >> 43, - ptn::lit(44) >> 44, - ptn::lit(45) >> 45, - ptn::lit(46) >> 46, - ptn::lit(47) >> 47, - ptn::lit(48) >> 48, - ptn::lit(49) >> 49, - ptn::lit(50) >> 50, - ptn::lit(51) >> 51, - ptn::lit(52) >> 52, - ptn::lit(53) >> 53, - ptn::lit(54) >> 54, - ptn::lit(55) >> 55, - ptn::lit(56) >> 56, - ptn::lit(57) >> 57, - ptn::lit(58) >> 58, - ptn::lit(59) >> 59, - ptn::lit(60) >> 60, - ptn::lit(61) >> 61, - ptn::lit(62) >> 62, - ptn::lit(63) >> 63, - ptn::lit(64) >> 64, - ptn::lit(65) >> 65, - ptn::lit(66) >> 66, - ptn::lit(67) >> 67, - ptn::lit(68) >> 68, - ptn::lit(69) >> 69, - ptn::lit(70) >> 70, - ptn::lit(71) >> 71, - ptn::lit(72) >> 72, - ptn::lit(73) >> 73, - ptn::lit(74) >> 74, - ptn::lit(75) >> 75, - ptn::lit(76) >> 76, - ptn::lit(77) >> 77, - ptn::lit(78) >> 78, - ptn::lit(79) >> 79, - ptn::lit(80) >> 80, - ptn::lit(81) >> 81, - ptn::lit(82) >> 82, - ptn::lit(83) >> 83, - ptn::lit(84) >> 84, - ptn::lit(85) >> 85, - ptn::lit(86) >> 86, - ptn::lit(87) >> 87, - ptn::lit(88) >> 88, - ptn::lit(89) >> 89, - ptn::lit(90) >> 90, - ptn::lit(91) >> 91, - ptn::lit(92) >> 92, - ptn::lit(93) >> 93, - ptn::lit(94) >> 94, - ptn::lit(95) >> 95, - ptn::lit(96) >> 96, - ptn::lit(97) >> 97, - ptn::lit(98) >> 98, - ptn::lit(99) >> 99, - ptn::lit(100) >> 100, - ptn::lit(101) >> 101, - ptn::lit(102) >> 102, - ptn::lit(103) >> 103, - ptn::lit(104) >> 104, - ptn::lit(105) >> 105, - ptn::lit(106) >> 106, - ptn::lit(107) >> 107, - ptn::lit(108) >> 108, - ptn::lit(109) >> 109, - ptn::lit(110) >> 110, - ptn::lit(111) >> 111, - ptn::lit(112) >> 112, - ptn::lit(113) >> 113, - ptn::lit(114) >> 114, - ptn::lit(115) >> 115, - ptn::lit(116) >> 116, - ptn::lit(117) >> 117, - ptn::lit(118) >> 118, - ptn::lit(119) >> 119, - ptn::lit(120) >> 120, - ptn::lit(121) >> 121, - ptn::lit(122) >> 122, - ptn::lit(123) >> 123, - ptn::lit(124) >> 124, - ptn::lit(125) >> 125, - ptn::lit(126) >> 126, - ptn::lit(127) >> 127, - __ >> 0); + return match(v) | on( + ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + ptn::lit(64) >> 64, + ptn::lit(65) >> 65, + ptn::lit(66) >> 66, + ptn::lit(67) >> 67, + ptn::lit(68) >> 68, + ptn::lit(69) >> 69, + ptn::lit(70) >> 70, + ptn::lit(71) >> 71, + ptn::lit(72) >> 72, + ptn::lit(73) >> 73, + ptn::lit(74) >> 74, + ptn::lit(75) >> 75, + ptn::lit(76) >> 76, + ptn::lit(77) >> 77, + ptn::lit(78) >> 78, + ptn::lit(79) >> 79, + ptn::lit(80) >> 80, + ptn::lit(81) >> 81, + ptn::lit(82) >> 82, + ptn::lit(83) >> 83, + ptn::lit(84) >> 84, + ptn::lit(85) >> 85, + ptn::lit(86) >> 86, + ptn::lit(87) >> 87, + ptn::lit(88) >> 88, + ptn::lit(89) >> 89, + ptn::lit(90) >> 90, + ptn::lit(91) >> 91, + ptn::lit(92) >> 92, + ptn::lit(93) >> 93, + ptn::lit(94) >> 94, + ptn::lit(95) >> 95, + ptn::lit(96) >> 96, + ptn::lit(97) >> 97, + ptn::lit(98) >> 98, + ptn::lit(99) >> 99, + ptn::lit(100) >> 100, + ptn::lit(101) >> 101, + ptn::lit(102) >> 102, + ptn::lit(103) >> 103, + ptn::lit(104) >> 104, + ptn::lit(105) >> 105, + ptn::lit(106) >> 106, + ptn::lit(107) >> 107, + ptn::lit(108) >> 108, + ptn::lit(109) >> 109, + ptn::lit(110) >> 110, + ptn::lit(111) >> 111, + ptn::lit(112) >> 112, + ptn::lit(113) >> 113, + ptn::lit(114) >> 114, + ptn::lit(115) >> 115, + ptn::lit(116) >> 116, + ptn::lit(117) >> 117, + ptn::lit(118) >> 118, + ptn::lit(119) >> 119, + ptn::lit(120) >> 120, + ptn::lit(121) >> 121, + ptn::lit(122) >> 122, + ptn::lit(123) >> 123, + ptn::lit(124) >> 124, + ptn::lit(125) >> 125, + ptn::lit(126) >> 126, + ptn::lit(127) >> 127, + __ >> 0); } static_assert(ct_lit_128(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_16.cpp b/bench/compile-time/ct_bench_lit_16.cpp index 36b9d69..b0c831c 100644 --- a/bench/compile-time/ct_bench_lit_16.cpp +++ b/bench/compile-time/ct_bench_lit_16.cpp @@ -5,24 +5,24 @@ constexpr int ct_lit_16(int v) noexcept { using namespace ptn; - return match(v) - | on(ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - __ >> 0); + return match(v) | on( + ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + __ >> 0); } static_assert(ct_lit_16(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_32.cpp b/bench/compile-time/ct_bench_lit_32.cpp index 3221391..f97752d 100644 --- a/bench/compile-time/ct_bench_lit_32.cpp +++ b/bench/compile-time/ct_bench_lit_32.cpp @@ -5,40 +5,40 @@ constexpr int ct_lit_32(int v) noexcept { using namespace ptn; - return match(v) - | on(ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - __ >> 0); + return match(v) | on( + ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + __ >> 0); } static_assert(ct_lit_32(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_64.cpp b/bench/compile-time/ct_bench_lit_64.cpp index fe78b18..36eba10 100644 --- a/bench/compile-time/ct_bench_lit_64.cpp +++ b/bench/compile-time/ct_bench_lit_64.cpp @@ -5,72 +5,72 @@ constexpr int ct_lit_64(int v) noexcept { using namespace ptn; - return match(v) - | on(ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - ptn::lit(32) >> 32, - ptn::lit(33) >> 33, - ptn::lit(34) >> 34, - ptn::lit(35) >> 35, - ptn::lit(36) >> 36, - ptn::lit(37) >> 37, - ptn::lit(38) >> 38, - ptn::lit(39) >> 39, - ptn::lit(40) >> 40, - ptn::lit(41) >> 41, - ptn::lit(42) >> 42, - ptn::lit(43) >> 43, - ptn::lit(44) >> 44, - ptn::lit(45) >> 45, - ptn::lit(46) >> 46, - ptn::lit(47) >> 47, - ptn::lit(48) >> 48, - ptn::lit(49) >> 49, - ptn::lit(50) >> 50, - ptn::lit(51) >> 51, - ptn::lit(52) >> 52, - ptn::lit(53) >> 53, - ptn::lit(54) >> 54, - ptn::lit(55) >> 55, - ptn::lit(56) >> 56, - ptn::lit(57) >> 57, - ptn::lit(58) >> 58, - ptn::lit(59) >> 59, - ptn::lit(60) >> 60, - ptn::lit(61) >> 61, - ptn::lit(62) >> 62, - ptn::lit(63) >> 63, - __ >> 0); + return match(v) | on( + ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + __ >> 0); } static_assert(ct_lit_64(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_8.cpp b/bench/compile-time/ct_bench_lit_8.cpp index 752afae..1ff3238 100644 --- a/bench/compile-time/ct_bench_lit_8.cpp +++ b/bench/compile-time/ct_bench_lit_8.cpp @@ -5,16 +5,16 @@ constexpr int ct_lit_8(int v) noexcept { using namespace ptn; - return match(v) - | on(ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - __ >> 0); + return match(v) | on( + ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + __ >> 0); } static_assert(ct_lit_8(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_rdense.cpp b/bench/compile-time/ct_bench_lit_rdense.cpp new file mode 100644 index 0000000..ad03155 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_rdense.cpp @@ -0,0 +1,24 @@ +#include "ptn/patternia.hpp" + +namespace { + constexpr int ct_lit_rdense(int v) noexcept { + using namespace ptn; + return match(v) | on( + ptn::lit(0) >> 0, ptn::lit(7) >> 7, + ptn::lit(14) >> 14, ptn::lit(21) >> 21, + ptn::lit(28) >> 28, ptn::lit(35) >> 35, + ptn::lit(42) >> 42, ptn::lit(49) >> 49, + ptn::lit(56) >> 56, ptn::lit(63) >> 63, + ptn::lit(70) >> 70, ptn::lit(77) >> 77, + ptn::lit(84) >> 84, ptn::lit(91) >> 91, + ptn::lit(98) >> 98, + __ >> 0); + } + + static_assert(ct_lit_rdense(0) == 0, ""); + static_assert(ct_lit_rdense(49) == 49, ""); + static_assert(ct_lit_rdense(98) == 98, ""); + static_assert(ct_lit_rdense(-1) == 0, ""); + static_assert(ct_lit_rdense(99) == 0, ""); + static_assert(ct_lit_rdense(35) == 35, ""); +} diff --git a/include/ptn/core/common/eval.hpp b/include/ptn/core/common/eval.hpp index 801ea39..1b12eaa 100644 --- a/include/ptn/core/common/eval.hpp +++ b/include/ptn/core/common/eval.hpp @@ -1137,7 +1137,9 @@ namespace ptn::core::common { CasesTuple &&cases, Otherwise &&otherwise_handler) { if constexpr (Plan::kind - == dispatch_plan_kind::static_literal_dense) { + == dispatch_plan_kind::static_literal_dense + || Plan::kind + == dispatch_plan_kind::literal_runtime_dense) { return eval_cases_impl_static_literal_dispatch( subject, std::forward(cases), diff --git a/include/ptn/core/common/optimize.hpp b/include/ptn/core/common/optimize.hpp index 7fcafbe..aa20ce4 100644 --- a/include/ptn/core/common/optimize.hpp +++ b/include/ptn/core/common/optimize.hpp @@ -102,6 +102,11 @@ namespace ptn::core::common { constexpr std::size_t k_static_literal_dense_dispatch_max_span = 512; constexpr std::size_t k_static_literal_dense_dispatch_max_density = 4; + constexpr std::size_t k_runtime_literal_dense_dispatch_max_cases = 256; + constexpr std::size_t k_runtime_literal_dense_dispatch_max_span = 512; + constexpr std::size_t k_runtime_literal_dense_dispatch_max_density = 8; + constexpr std::size_t k_runtime_literal_dense_dispatch_min_cases = 4; + enum class variant_dispatch_tier { hot_inline, warm_segmented, @@ -1138,6 +1143,19 @@ namespace ptn::core::common { && range_size <= literal_case_count * k_static_literal_dense_dispatch_max_density; + + // Runtime dense dispatch relaxes the density heuristic for cases + // where the span is still manageable but the static density check + // would reject a worthwhile O(1) table. + static constexpr bool use_runtime_dense_table = + has_literal_cases + && literal_case_count + >= k_runtime_literal_dense_dispatch_min_cases + && case_count <= k_runtime_literal_dense_dispatch_max_cases + && range_size <= k_runtime_literal_dense_dispatch_max_span + && range_size + <= literal_case_count + * k_runtime_literal_dense_dispatch_max_density; }; template @@ -1149,6 +1167,17 @@ namespace ptn::core::common { static_literal_dispatch_metadata:: use_dense_table> {}; + template + struct is_runtime_literal_dense_dispatch_enabled : std::false_type {}; + + template + struct is_runtime_literal_dense_dispatch_enabled + : std::bool_constant< + static_literal_dispatch_metadata:: + use_runtime_dense_table> {}; + // Summarizes the three lowering rules discussed for the engine. // `full` means direct keyed dispatch is legal, `bucketed` means keyed // dispatch may narrow the search before replay, and `none` means the @@ -1165,6 +1194,7 @@ namespace ptn::core::common { sequential, literal_linear, static_literal_dense, + literal_runtime_dense, variant_simple, variant_alt_bucketed }; @@ -1212,6 +1242,18 @@ namespace ptn::core::common { supports_static_literal_dispatch && ...))>::value; + // Rule 1: literal cases that satisfy the relaxed density heuristic + // can use O(1) array-based dispatch even when the stricter static + // dense check fails. + static constexpr bool can_use_runtime_literal_dense_dispatch = + is_runtime_literal_dense_dispatch_enabled< + subject_t, + std::tuple, + ((std::is_integral_v || std::is_enum_v) + && (case_analysis:: + supports_static_literal_dispatch + && ...))>::value; + // Rule 1: every case participates in direct runtime literal dispatch. // This is the older linear literal lowering. It stays available when // dense static-literal dispatch is not legal or not profitable. @@ -1240,7 +1282,9 @@ namespace ptn::core::common { // - Otherwise a variant subject may still use `bucketed`. // - Everything else falls back to sequential evaluation. static constexpr lowering_legality legality = - can_use_static_literal_dispatch || can_use_simple_literal_dispatch + can_use_static_literal_dispatch + || can_use_runtime_literal_dense_dispatch + || can_use_simple_literal_dispatch || can_use_simple_variant_dispatch ? lowering_legality::full : (can_use_variant_alt_dispatch ? lowering_legality::bucketed @@ -1258,6 +1302,8 @@ namespace ptn::core::common { // Re-exports the sequence IR in the shape expected by plan selection. static constexpr bool can_use_static_literal_dispatch = ir_t::can_use_static_literal_dispatch; + static constexpr bool can_use_runtime_literal_dense_dispatch = + ir_t::can_use_runtime_literal_dense_dispatch; static constexpr bool can_use_simple_literal_dispatch = ir_t::can_use_simple_literal_dispatch; static constexpr bool can_use_simple_variant_dispatch = @@ -1426,6 +1472,39 @@ namespace ptn::core::common { }; }; + template >> + struct literal_runtime_dense_dispatch_plan { + using subject_t = SubjectValue; + using cases_tuple_t = std::remove_reference_t; + using metadata_t = + static_literal_dispatch_metadata; + using key_t = typename metadata_t::key_t; + using case_index_t = typename metadata_t::case_index_t; + + static constexpr dispatch_plan_kind kind = + dispatch_plan_kind::literal_runtime_dense; + static constexpr lowering_legality legality = + lowering_legality::full; + + static constexpr std::size_t case_count = + metadata_t::case_count; + static constexpr std::size_t literal_case_count = + metadata_t::literal_case_count; + static constexpr std::size_t range_size = + metadata_t::range_size; + static constexpr bool has_default_case = + metadata_t::has_default_case; + static constexpr key_t min_value = metadata_t::min_value; + static constexpr key_t max_value = metadata_t::max_value; + static constexpr case_index_t default_case_index = + metadata_t::default_case_index; + static constexpr case_index_t k_invalid_case_index = + metadata_t::k_invalid_case_index; + }; + template @@ -1452,6 +1531,14 @@ namespace ptn::core::common { using type = static_literal_dense_dispatch_plan; }; + template + struct dispatch_plan_for_kind { + using type = + literal_runtime_dense_dispatch_plan; + }; + template struct dispatch_plan_for_kind Date: Thu, 30 Apr 2026 17:36:20 +0000 Subject: [PATCH 3/3] style: apply clang-format to PR changes --- bench/bench_suite.cpp | 126 +- bench/compile-time/ct_bench_lit_128.cpp | 260 ++-- bench/compile-time/ct_bench_lit_16.cpp | 36 +- bench/compile-time/ct_bench_lit_32.cpp | 68 +- bench/compile-time/ct_bench_lit_64.cpp | 132 +- bench/compile-time/ct_bench_lit_8.cpp | 20 +- bench/compile-time/ct_bench_lit_rdense.cpp | 35 +- include/ptn/core/common/eval.hpp | 19 +- include/ptn/core/common/optimize.hpp | 1396 +++++++++++--------- 9 files changed, 1164 insertions(+), 928 deletions(-) diff --git a/bench/bench_suite.cpp b/bench/bench_suite.cpp index 36991fa..8cdf166 100644 --- a/bench/bench_suite.cpp +++ b/bench/bench_suite.cpp @@ -956,54 +956,92 @@ namespace { static int patternia_pipe_literal_match_rdense_route(int x) { using namespace ptn; return match(x) - | on(ptn::lit(0) >> 0, ptn::lit(7) >> 7, - ptn::lit(14) >> 14, ptn::lit(21) >> 21, - ptn::lit(28) >> 28, ptn::lit(35) >> 35, - ptn::lit(42) >> 42, ptn::lit(49) >> 49, - ptn::lit(56) >> 56, ptn::lit(63) >> 63, - ptn::lit(70) >> 70, ptn::lit(77) >> 77, - ptn::lit(84) >> 84, ptn::lit(91) >> 91, + | on(ptn::lit(0) >> 0, + ptn::lit(7) >> 7, + ptn::lit(14) >> 14, + ptn::lit(21) >> 21, + ptn::lit(28) >> 28, + ptn::lit(35) >> 35, + ptn::lit(42) >> 42, + ptn::lit(49) >> 49, + ptn::lit(56) >> 56, + ptn::lit(63) >> 63, + ptn::lit(70) >> 70, + ptn::lit(77) >> 77, + ptn::lit(84) >> 84, + ptn::lit(91) >> 91, ptn::lit(98) >> 98, __ >> 0); } static int if_else_literal_match_rdense_route(int x) { - if (x == 0) return 0; - if (x == 7) return 7; - if (x == 14) return 14; - if (x == 21) return 21; - if (x == 28) return 28; - if (x == 35) return 35; - if (x == 42) return 42; - if (x == 49) return 49; - if (x == 56) return 56; - if (x == 63) return 63; - if (x == 70) return 70; - if (x == 77) return 77; - if (x == 84) return 84; - if (x == 91) return 91; - if (x == 98) return 98; + if (x == 0) + return 0; + if (x == 7) + return 7; + if (x == 14) + return 14; + if (x == 21) + return 21; + if (x == 28) + return 28; + if (x == 35) + return 35; + if (x == 42) + return 42; + if (x == 49) + return 49; + if (x == 56) + return 56; + if (x == 63) + return 63; + if (x == 70) + return 70; + if (x == 77) + return 77; + if (x == 84) + return 84; + if (x == 91) + return 91; + if (x == 98) + return 98; return 0; } static int switch_literal_match_rdense_route(int x) { switch (x) { - case 0: return 0; - case 7: return 7; - case 14: return 14; - case 21: return 21; - case 28: return 28; - case 35: return 35; - case 42: return 42; - case 49: return 49; - case 56: return 56; - case 63: return 63; - case 70: return 70; - case 77: return 77; - case 84: return 84; - case 91: return 91; - case 98: return 98; - default: return 0; + case 0: + return 0; + case 7: + return 7; + case 14: + return 14; + case 21: + return 21; + case 28: + return 28; + case 35: + return 35; + case 42: + return 42; + case 49: + return 49; + case 56: + return 56; + case 63: + return 63; + case 70: + return 70; + case 77: + return 77; + case 84: + return 84; + case 91: + return 91; + case 98: + return 98; + default: + return 0; } } @@ -1579,12 +1617,12 @@ namespace { static const std::vector &literal_rdense_workload() { static const std::vector data = { - 0, 7, 14, 21, 28, 35, 42, 49, 56, 63, - 70, 77, 84, 91, 98, // all 15 sparse values - 3, 10, 17, 31, 55, 99, // misses - -1, 100, // out-of-range - 98, 91, 84, 77, 70, 63, 56, 49, 42, 35, - 28, 21, 14, 7, 0, // reverse hits + 0, 7, 14, 21, 28, 35, 42, 49, + 56, 63, 70, 77, 84, 91, 98, // all 15 sparse values + 3, 10, 17, 31, 55, 99, // misses + -1, 100, // out-of-range + 98, 91, 84, 77, 70, 63, 56, 49, + 42, 35, 28, 21, 14, 7, 0, // reverse hits }; return data; } diff --git a/bench/compile-time/ct_bench_lit_128.cpp b/bench/compile-time/ct_bench_lit_128.cpp index 3a05a94..c7754b9 100644 --- a/bench/compile-time/ct_bench_lit_128.cpp +++ b/bench/compile-time/ct_bench_lit_128.cpp @@ -5,136 +5,136 @@ constexpr int ct_lit_128(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - ptn::lit(32) >> 32, - ptn::lit(33) >> 33, - ptn::lit(34) >> 34, - ptn::lit(35) >> 35, - ptn::lit(36) >> 36, - ptn::lit(37) >> 37, - ptn::lit(38) >> 38, - ptn::lit(39) >> 39, - ptn::lit(40) >> 40, - ptn::lit(41) >> 41, - ptn::lit(42) >> 42, - ptn::lit(43) >> 43, - ptn::lit(44) >> 44, - ptn::lit(45) >> 45, - ptn::lit(46) >> 46, - ptn::lit(47) >> 47, - ptn::lit(48) >> 48, - ptn::lit(49) >> 49, - ptn::lit(50) >> 50, - ptn::lit(51) >> 51, - ptn::lit(52) >> 52, - ptn::lit(53) >> 53, - ptn::lit(54) >> 54, - ptn::lit(55) >> 55, - ptn::lit(56) >> 56, - ptn::lit(57) >> 57, - ptn::lit(58) >> 58, - ptn::lit(59) >> 59, - ptn::lit(60) >> 60, - ptn::lit(61) >> 61, - ptn::lit(62) >> 62, - ptn::lit(63) >> 63, - ptn::lit(64) >> 64, - ptn::lit(65) >> 65, - ptn::lit(66) >> 66, - ptn::lit(67) >> 67, - ptn::lit(68) >> 68, - ptn::lit(69) >> 69, - ptn::lit(70) >> 70, - ptn::lit(71) >> 71, - ptn::lit(72) >> 72, - ptn::lit(73) >> 73, - ptn::lit(74) >> 74, - ptn::lit(75) >> 75, - ptn::lit(76) >> 76, - ptn::lit(77) >> 77, - ptn::lit(78) >> 78, - ptn::lit(79) >> 79, - ptn::lit(80) >> 80, - ptn::lit(81) >> 81, - ptn::lit(82) >> 82, - ptn::lit(83) >> 83, - ptn::lit(84) >> 84, - ptn::lit(85) >> 85, - ptn::lit(86) >> 86, - ptn::lit(87) >> 87, - ptn::lit(88) >> 88, - ptn::lit(89) >> 89, - ptn::lit(90) >> 90, - ptn::lit(91) >> 91, - ptn::lit(92) >> 92, - ptn::lit(93) >> 93, - ptn::lit(94) >> 94, - ptn::lit(95) >> 95, - ptn::lit(96) >> 96, - ptn::lit(97) >> 97, - ptn::lit(98) >> 98, - ptn::lit(99) >> 99, - ptn::lit(100) >> 100, - ptn::lit(101) >> 101, - ptn::lit(102) >> 102, - ptn::lit(103) >> 103, - ptn::lit(104) >> 104, - ptn::lit(105) >> 105, - ptn::lit(106) >> 106, - ptn::lit(107) >> 107, - ptn::lit(108) >> 108, - ptn::lit(109) >> 109, - ptn::lit(110) >> 110, - ptn::lit(111) >> 111, - ptn::lit(112) >> 112, - ptn::lit(113) >> 113, - ptn::lit(114) >> 114, - ptn::lit(115) >> 115, - ptn::lit(116) >> 116, - ptn::lit(117) >> 117, - ptn::lit(118) >> 118, - ptn::lit(119) >> 119, - ptn::lit(120) >> 120, - ptn::lit(121) >> 121, - ptn::lit(122) >> 122, - ptn::lit(123) >> 123, - ptn::lit(124) >> 124, - ptn::lit(125) >> 125, - ptn::lit(126) >> 126, - ptn::lit(127) >> 127, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + ptn::lit(64) >> 64, + ptn::lit(65) >> 65, + ptn::lit(66) >> 66, + ptn::lit(67) >> 67, + ptn::lit(68) >> 68, + ptn::lit(69) >> 69, + ptn::lit(70) >> 70, + ptn::lit(71) >> 71, + ptn::lit(72) >> 72, + ptn::lit(73) >> 73, + ptn::lit(74) >> 74, + ptn::lit(75) >> 75, + ptn::lit(76) >> 76, + ptn::lit(77) >> 77, + ptn::lit(78) >> 78, + ptn::lit(79) >> 79, + ptn::lit(80) >> 80, + ptn::lit(81) >> 81, + ptn::lit(82) >> 82, + ptn::lit(83) >> 83, + ptn::lit(84) >> 84, + ptn::lit(85) >> 85, + ptn::lit(86) >> 86, + ptn::lit(87) >> 87, + ptn::lit(88) >> 88, + ptn::lit(89) >> 89, + ptn::lit(90) >> 90, + ptn::lit(91) >> 91, + ptn::lit(92) >> 92, + ptn::lit(93) >> 93, + ptn::lit(94) >> 94, + ptn::lit(95) >> 95, + ptn::lit(96) >> 96, + ptn::lit(97) >> 97, + ptn::lit(98) >> 98, + ptn::lit(99) >> 99, + ptn::lit(100) >> 100, + ptn::lit(101) >> 101, + ptn::lit(102) >> 102, + ptn::lit(103) >> 103, + ptn::lit(104) >> 104, + ptn::lit(105) >> 105, + ptn::lit(106) >> 106, + ptn::lit(107) >> 107, + ptn::lit(108) >> 108, + ptn::lit(109) >> 109, + ptn::lit(110) >> 110, + ptn::lit(111) >> 111, + ptn::lit(112) >> 112, + ptn::lit(113) >> 113, + ptn::lit(114) >> 114, + ptn::lit(115) >> 115, + ptn::lit(116) >> 116, + ptn::lit(117) >> 117, + ptn::lit(118) >> 118, + ptn::lit(119) >> 119, + ptn::lit(120) >> 120, + ptn::lit(121) >> 121, + ptn::lit(122) >> 122, + ptn::lit(123) >> 123, + ptn::lit(124) >> 124, + ptn::lit(125) >> 125, + ptn::lit(126) >> 126, + ptn::lit(127) >> 127, + __ >> 0); } static_assert(ct_lit_128(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_16.cpp b/bench/compile-time/ct_bench_lit_16.cpp index b0c831c..36b9d69 100644 --- a/bench/compile-time/ct_bench_lit_16.cpp +++ b/bench/compile-time/ct_bench_lit_16.cpp @@ -5,24 +5,24 @@ constexpr int ct_lit_16(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + __ >> 0); } static_assert(ct_lit_16(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_32.cpp b/bench/compile-time/ct_bench_lit_32.cpp index f97752d..3221391 100644 --- a/bench/compile-time/ct_bench_lit_32.cpp +++ b/bench/compile-time/ct_bench_lit_32.cpp @@ -5,40 +5,40 @@ constexpr int ct_lit_32(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + __ >> 0); } static_assert(ct_lit_32(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_64.cpp b/bench/compile-time/ct_bench_lit_64.cpp index 36eba10..fe78b18 100644 --- a/bench/compile-time/ct_bench_lit_64.cpp +++ b/bench/compile-time/ct_bench_lit_64.cpp @@ -5,72 +5,72 @@ constexpr int ct_lit_64(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - ptn::lit(8) >> 8, - ptn::lit(9) >> 9, - ptn::lit(10) >> 10, - ptn::lit(11) >> 11, - ptn::lit(12) >> 12, - ptn::lit(13) >> 13, - ptn::lit(14) >> 14, - ptn::lit(15) >> 15, - ptn::lit(16) >> 16, - ptn::lit(17) >> 17, - ptn::lit(18) >> 18, - ptn::lit(19) >> 19, - ptn::lit(20) >> 20, - ptn::lit(21) >> 21, - ptn::lit(22) >> 22, - ptn::lit(23) >> 23, - ptn::lit(24) >> 24, - ptn::lit(25) >> 25, - ptn::lit(26) >> 26, - ptn::lit(27) >> 27, - ptn::lit(28) >> 28, - ptn::lit(29) >> 29, - ptn::lit(30) >> 30, - ptn::lit(31) >> 31, - ptn::lit(32) >> 32, - ptn::lit(33) >> 33, - ptn::lit(34) >> 34, - ptn::lit(35) >> 35, - ptn::lit(36) >> 36, - ptn::lit(37) >> 37, - ptn::lit(38) >> 38, - ptn::lit(39) >> 39, - ptn::lit(40) >> 40, - ptn::lit(41) >> 41, - ptn::lit(42) >> 42, - ptn::lit(43) >> 43, - ptn::lit(44) >> 44, - ptn::lit(45) >> 45, - ptn::lit(46) >> 46, - ptn::lit(47) >> 47, - ptn::lit(48) >> 48, - ptn::lit(49) >> 49, - ptn::lit(50) >> 50, - ptn::lit(51) >> 51, - ptn::lit(52) >> 52, - ptn::lit(53) >> 53, - ptn::lit(54) >> 54, - ptn::lit(55) >> 55, - ptn::lit(56) >> 56, - ptn::lit(57) >> 57, - ptn::lit(58) >> 58, - ptn::lit(59) >> 59, - ptn::lit(60) >> 60, - ptn::lit(61) >> 61, - ptn::lit(62) >> 62, - ptn::lit(63) >> 63, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + ptn::lit(8) >> 8, + ptn::lit(9) >> 9, + ptn::lit(10) >> 10, + ptn::lit(11) >> 11, + ptn::lit(12) >> 12, + ptn::lit(13) >> 13, + ptn::lit(14) >> 14, + ptn::lit(15) >> 15, + ptn::lit(16) >> 16, + ptn::lit(17) >> 17, + ptn::lit(18) >> 18, + ptn::lit(19) >> 19, + ptn::lit(20) >> 20, + ptn::lit(21) >> 21, + ptn::lit(22) >> 22, + ptn::lit(23) >> 23, + ptn::lit(24) >> 24, + ptn::lit(25) >> 25, + ptn::lit(26) >> 26, + ptn::lit(27) >> 27, + ptn::lit(28) >> 28, + ptn::lit(29) >> 29, + ptn::lit(30) >> 30, + ptn::lit(31) >> 31, + ptn::lit(32) >> 32, + ptn::lit(33) >> 33, + ptn::lit(34) >> 34, + ptn::lit(35) >> 35, + ptn::lit(36) >> 36, + ptn::lit(37) >> 37, + ptn::lit(38) >> 38, + ptn::lit(39) >> 39, + ptn::lit(40) >> 40, + ptn::lit(41) >> 41, + ptn::lit(42) >> 42, + ptn::lit(43) >> 43, + ptn::lit(44) >> 44, + ptn::lit(45) >> 45, + ptn::lit(46) >> 46, + ptn::lit(47) >> 47, + ptn::lit(48) >> 48, + ptn::lit(49) >> 49, + ptn::lit(50) >> 50, + ptn::lit(51) >> 51, + ptn::lit(52) >> 52, + ptn::lit(53) >> 53, + ptn::lit(54) >> 54, + ptn::lit(55) >> 55, + ptn::lit(56) >> 56, + ptn::lit(57) >> 57, + ptn::lit(58) >> 58, + ptn::lit(59) >> 59, + ptn::lit(60) >> 60, + ptn::lit(61) >> 61, + ptn::lit(62) >> 62, + ptn::lit(63) >> 63, + __ >> 0); } static_assert(ct_lit_64(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_8.cpp b/bench/compile-time/ct_bench_lit_8.cpp index 1ff3238..752afae 100644 --- a/bench/compile-time/ct_bench_lit_8.cpp +++ b/bench/compile-time/ct_bench_lit_8.cpp @@ -5,16 +5,16 @@ constexpr int ct_lit_8(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, - ptn::lit(1) >> 1, - ptn::lit(2) >> 2, - ptn::lit(3) >> 3, - ptn::lit(4) >> 4, - ptn::lit(5) >> 5, - ptn::lit(6) >> 6, - ptn::lit(7) >> 7, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(1) >> 1, + ptn::lit(2) >> 2, + ptn::lit(3) >> 3, + ptn::lit(4) >> 4, + ptn::lit(5) >> 5, + ptn::lit(6) >> 6, + ptn::lit(7) >> 7, + __ >> 0); } static_assert(ct_lit_8(0) == 0, ""); diff --git a/bench/compile-time/ct_bench_lit_rdense.cpp b/bench/compile-time/ct_bench_lit_rdense.cpp index ad03155..a8fbc55 100644 --- a/bench/compile-time/ct_bench_lit_rdense.cpp +++ b/bench/compile-time/ct_bench_lit_rdense.cpp @@ -3,22 +3,29 @@ namespace { constexpr int ct_lit_rdense(int v) noexcept { using namespace ptn; - return match(v) | on( - ptn::lit(0) >> 0, ptn::lit(7) >> 7, - ptn::lit(14) >> 14, ptn::lit(21) >> 21, - ptn::lit(28) >> 28, ptn::lit(35) >> 35, - ptn::lit(42) >> 42, ptn::lit(49) >> 49, - ptn::lit(56) >> 56, ptn::lit(63) >> 63, - ptn::lit(70) >> 70, ptn::lit(77) >> 77, - ptn::lit(84) >> 84, ptn::lit(91) >> 91, - ptn::lit(98) >> 98, - __ >> 0); + return match(v) + | on(ptn::lit(0) >> 0, + ptn::lit(7) >> 7, + ptn::lit(14) >> 14, + ptn::lit(21) >> 21, + ptn::lit(28) >> 28, + ptn::lit(35) >> 35, + ptn::lit(42) >> 42, + ptn::lit(49) >> 49, + ptn::lit(56) >> 56, + ptn::lit(63) >> 63, + ptn::lit(70) >> 70, + ptn::lit(77) >> 77, + ptn::lit(84) >> 84, + ptn::lit(91) >> 91, + ptn::lit(98) >> 98, + __ >> 0); } - static_assert(ct_lit_rdense(0) == 0, ""); + static_assert(ct_lit_rdense(0) == 0, ""); static_assert(ct_lit_rdense(49) == 49, ""); static_assert(ct_lit_rdense(98) == 98, ""); - static_assert(ct_lit_rdense(-1) == 0, ""); - static_assert(ct_lit_rdense(99) == 0, ""); + static_assert(ct_lit_rdense(-1) == 0, ""); + static_assert(ct_lit_rdense(99) == 0, ""); static_assert(ct_lit_rdense(35) == 35, ""); -} +} // namespace diff --git a/include/ptn/core/common/eval.hpp b/include/ptn/core/common/eval.hpp index 1b12eaa..f0c5ad8 100644 --- a/include/ptn/core/common/eval.hpp +++ b/include/ptn/core/common/eval.hpp @@ -342,7 +342,8 @@ namespace ptn::core::common { return static_cast(guarded.pred(bound)); } else { - return static_cast(invoke_from_tuple(guarded.pred, bound)); + return static_cast( + invoke_from_tuple(guarded.pred, bound)); } } @@ -651,8 +652,8 @@ namespace ptn::core::common { // Dispatches to the offset-specific handler through a dense jump // table. The switch is kept small because the lowering analysis - // already limits range_size to 512, and the compiler turns a dense - // integer switch into a jump table with no call overhead. + // already limits range_size to 512, and the compiler turns a + // dense integer switch into a jump table with no call overhead. template (subject); - const auto offset = static_cast(value) - - static_cast(Plan::min_value); + const key_t value = static_cast(subject); + const auto offset = static_cast(value) + - static_cast( + Plan::min_value); if (offset < Plan::range_size) { return dispatch_static_literal_offset( @@ -1137,9 +1139,10 @@ namespace ptn::core::common { CasesTuple &&cases, Otherwise &&otherwise_handler) { if constexpr (Plan::kind - == dispatch_plan_kind::static_literal_dense + == dispatch_plan_kind::static_literal_dense || Plan::kind - == dispatch_plan_kind::literal_runtime_dense) { + == dispatch_plan_kind:: + literal_runtime_dense) { return eval_cases_impl_static_literal_dispatch( subject, std::forward(cases), diff --git a/include/ptn/core/common/optimize.hpp b/include/ptn/core/common/optimize.hpp index aa20ce4..8e600df 100644 --- a/include/ptn/core/common/optimize.hpp +++ b/include/ptn/core/common/optimize.hpp @@ -49,12 +49,13 @@ namespace ptn::core::common { struct is_guarded_pattern : std::false_type {}; template - struct is_guarded_pattern> + struct is_guarded_pattern< + ptn::pat::mod::guarded_pattern> : std::true_type {}; template - constexpr bool - is_guarded_pattern_v = is_guarded_pattern>::value; + constexpr bool is_guarded_pattern_v = is_guarded_pattern< + std::decay_t>::value; template struct is_equality_comparable : std::false_type {}; @@ -68,8 +69,9 @@ namespace ptn::core::common { : std::true_type {}; template - constexpr bool is_equality_comparable_v = - is_equality_comparable::value; + constexpr bool is_equality_comparable_v = is_equality_comparable< + Lhs, + Rhs>::value; template > struct literal_subject_key { @@ -82,30 +84,42 @@ namespace ptn::core::common { }; template - using literal_subject_key_t = typename literal_subject_key::type; + using literal_subject_key_t = typename literal_subject_key< + T>::type; - // Detects std::variant subjects so lowering only engages on variants. + // Detects std::variant subjects so lowering only engages on + // variants. template struct is_variant_subject : std::false_type {}; template - struct is_variant_subject> : std::true_type {}; + struct is_variant_subject> + : std::true_type {}; template constexpr bool is_variant_subject_v = is_variant_subject< std::remove_cv_t>>::value; - constexpr std::size_t k_variant_inline_dispatch_alt_threshold = 16; - constexpr std::size_t k_variant_segmented_dispatch_alt_threshold = 64; + constexpr std::size_t + k_variant_inline_dispatch_alt_threshold = 16; + constexpr std::size_t + k_variant_segmented_dispatch_alt_threshold = 64; constexpr std::size_t k_variant_dispatch_segment_size = 16; - constexpr std::size_t k_static_literal_dense_dispatch_max_cases = 256; - constexpr std::size_t k_static_literal_dense_dispatch_max_span = 512; - constexpr std::size_t k_static_literal_dense_dispatch_max_density = 4; - - constexpr std::size_t k_runtime_literal_dense_dispatch_max_cases = 256; - constexpr std::size_t k_runtime_literal_dense_dispatch_max_span = 512; - constexpr std::size_t k_runtime_literal_dense_dispatch_max_density = 8; - constexpr std::size_t k_runtime_literal_dense_dispatch_min_cases = 4; + constexpr std::size_t + k_static_literal_dense_dispatch_max_cases = 256; + constexpr std::size_t + k_static_literal_dense_dispatch_max_span = 512; + constexpr std::size_t + k_static_literal_dense_dispatch_max_density = 4; + + constexpr std::size_t + k_runtime_literal_dense_dispatch_max_cases = 256; + constexpr std::size_t + k_runtime_literal_dense_dispatch_max_span = 512; + constexpr std::size_t + k_runtime_literal_dense_dispatch_max_density = 8; + constexpr std::size_t + k_runtime_literal_dense_dispatch_min_cases = 4; enum class variant_dispatch_tier { hot_inline, @@ -114,11 +128,14 @@ namespace ptn::core::common { }; template - constexpr variant_dispatch_tier variant_dispatch_tier_for_alt_count() { - if constexpr (AltCount <= k_variant_inline_dispatch_alt_threshold) { + constexpr variant_dispatch_tier + variant_dispatch_tier_for_alt_count() { + if constexpr (AltCount + <= k_variant_inline_dispatch_alt_threshold) { return variant_dispatch_tier::hot_inline; } - else if constexpr (AltCount <= k_variant_segmented_dispatch_alt_threshold) { + else if constexpr ( + AltCount <= k_variant_segmented_dispatch_alt_threshold) { return variant_dispatch_tier::warm_segmented; } else { @@ -126,16 +143,18 @@ namespace ptn::core::common { } } - // Compile-time binary dispatcher for variant alternative indexes. - // Keeps lowering analysis in optimize.hpp while evaluators provide - // concrete on-hit / on-miss behavior. + // Compile-time binary dispatcher for variant alternative + // indexes. Keeps lowering analysis in optimize.hpp while + // evaluators provide concrete on-hit / on-miss behavior. template struct variant_binary_alt_dispatch { template static constexpr decltype(auto) - dispatch(std::size_t active_index, OnHit &&on_hit, OnMiss &&on_miss) { + dispatch(std::size_t active_index, + OnHit &&on_hit, + OnMiss &&on_miss) { if constexpr (BeginAlt >= EndAlt || BeginAlt >= AltCount) { return std::forward(on_miss)(); } @@ -148,24 +167,22 @@ namespace ptn::core::common { return std::forward(on_miss)(); } else { - constexpr std::size_t - mid = BeginAlt + ((EndAlt - BeginAlt) / 2); + constexpr std::size_t mid = BeginAlt + + ((EndAlt - BeginAlt) / 2); if (active_index < mid) { - return variant_binary_alt_dispatch::dispatch( - active_index, - std::forward(on_hit), - std::forward(on_miss)); + return variant_binary_alt_dispatch< + BeginAlt, + mid, + AltCount>::dispatch(active_index, + std::forward(on_hit), + std::forward(on_miss)); } - return variant_binary_alt_dispatch::dispatch( - active_index, - std::forward(on_hit), - std::forward(on_miss)); + return variant_binary_alt_dispatch:: + dispatch(active_index, + std::forward(on_hit), + std::forward(on_miss)); } } }; @@ -181,8 +198,8 @@ namespace ptn::core::common { : std::true_type {}; template - constexpr bool - is_simple_variant_type_is_pattern_v = is_simple_variant_type_is_pattern< + constexpr bool is_simple_variant_type_is_pattern_v = + is_simple_variant_type_is_pattern< std::decay_t>::value; // Matches `alt()` with no subpattern/binding. @@ -197,7 +214,8 @@ namespace ptn::core::common { template constexpr bool is_simple_variant_type_alt_pattern_v = - is_simple_variant_type_alt_pattern>::value; + is_simple_variant_type_alt_pattern< + std::decay_t>::value; // Matches `is(...)` (any subpattern). template @@ -209,8 +227,9 @@ namespace ptn::core::common { : std::true_type {}; template - constexpr bool is_variant_type_is_pattern_v = is_variant_type_is_pattern< - std::decay_t>::value; + constexpr bool + is_variant_type_is_pattern_v = is_variant_type_is_pattern< + std::decay_t>::value; // Matches `alt(...)` (any subpattern). template @@ -222,14 +241,15 @@ namespace ptn::core::common { : std::true_type {}; template - constexpr bool is_variant_type_alt_pattern_v = is_variant_type_alt_pattern< - std::decay_t>::value; + constexpr bool + is_variant_type_alt_pattern_v = is_variant_type_alt_pattern< + std::decay_t>::value; // Wildcard can act as default arm in dispatcher fast paths. template - constexpr bool - is_wildcard_pattern_v = std::is_same_v, - ptn::pat::detail::wildcard_t>; + constexpr bool is_wildcard_pattern_v = std::is_same_v< + std::decay_t, + ptn::pat::detail::wildcard_t>; template constexpr bool is_simple_variant_dispatch_pattern_v = @@ -237,14 +257,15 @@ namespace ptn::core::common { || is_simple_variant_type_alt_pattern_v || is_wildcard_pattern_v; - // Matches runtime literal_pattern with default equality comparator. + // Matches runtime literal_pattern with default equality + // comparator. template struct is_simple_literal_pattern : std::false_type {}; template - struct is_simple_literal_pattern>> : std::true_type {}; + struct is_simple_literal_pattern< + ptn::pat::detail::literal_pattern>> + : std::true_type {}; template constexpr bool @@ -255,9 +276,9 @@ namespace ptn::core::common { struct is_static_literal_pattern : std::false_type {}; template - struct is_static_literal_pattern>> : std::true_type {}; + struct is_static_literal_pattern< + ptn::pat::detail::static_literal_pattern>> + : std::true_type {}; template constexpr bool @@ -266,13 +287,16 @@ namespace ptn::core::common { template constexpr bool is_simple_literal_dispatch_pattern_v = - is_simple_literal_pattern_v || is_wildcard_pattern_v; + is_simple_literal_pattern_v + || is_wildcard_pattern_v; template struct is_simple_literal_subject_compatible : std::false_type {}; template - struct is_simple_literal_subject_compatible + struct is_simple_literal_subject_compatible : std::bool_constant::store_t>> {}; @@ -281,14 +305,16 @@ namespace ptn::core::common { struct is_static_literal_subject_compatible : std::false_type {}; template - struct is_static_literal_subject_compatible + struct is_static_literal_subject_compatible : std::bool_constant< is_equality_comparable_v< Subject, typename std::decay_t::store_t> && std::is_constructible_v< - literal_subject_key_t< - std::remove_cv_t>>, + literal_subject_key_t>>, decltype(std::decay_t::value)>> {}; template @@ -298,8 +324,9 @@ namespace ptn::core::common { struct static_literal_case_value< ptn::pat::detail::static_literal_pattern>, Subject> { - using subject_t = std::remove_cv_t>; - using key_t = literal_subject_key_t; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; + using key_t = literal_subject_key_t; inline static constexpr key_t value = static_cast(V); }; @@ -309,9 +336,9 @@ namespace ptn::core::common { struct simple_variant_alt_index; template - struct simple_variant_alt_index> + struct simple_variant_alt_index< + ptn::pat::detail:: + type_alt_pattern> : std::integral_constant {}; template @@ -328,43 +355,53 @@ namespace ptn::core::common { template struct is_variant_direct_ref_bind_pattern< - ptn::pat::detail::type_is_pattern> + ptn::pat::detail:: + type_is_pattern> : std::true_type {}; // Matches `$(alt())`. template struct is_variant_direct_ref_bind_pattern< - ptn::pat::detail::type_alt_pattern> + ptn::pat::detail:: + type_alt_pattern> : std::true_type {}; template constexpr bool is_variant_direct_ref_bind_pattern_v = - is_variant_direct_ref_bind_pattern>::value; + is_variant_direct_ref_bind_pattern< + std::decay_t>::value; template struct guarded_inner_pattern; template - struct guarded_inner_pattern> { + struct guarded_inner_pattern< + ptn::pat::mod::guarded_pattern> { using type = Inner; }; - // Compile-time matcher for simple variant patterns by active alternative. - template + // Compile-time matcher for simple variant patterns by active + // alternative. + template constexpr bool simple_variant_pattern_matches_alt_index() { using pattern_t = std::decay_t; if constexpr (is_wildcard_pattern_v) { return true; } - else if constexpr (is_simple_variant_type_is_pattern_v) { - static_assert_variant_alt_unique(); - return ActiveIndex == pattern_t::template alt_index(); + else if constexpr (is_simple_variant_type_is_pattern_v< + pattern_t>) { + static_assert_variant_alt_unique(); + return ActiveIndex + == pattern_t::template alt_index(); } - else if constexpr (is_simple_variant_type_alt_pattern_v) { - constexpr std::size_t I = simple_variant_alt_index::value; + else if constexpr (is_simple_variant_type_alt_pattern_v< + pattern_t>) { + constexpr std::size_t + I = simple_variant_alt_index::value; static_assert_variant_alt_index(); return ActiveIndex == I; } @@ -374,7 +411,9 @@ namespace ptn::core::common { } // Compile-time prefilter by active variant alternative. - template + template constexpr bool variant_pattern_maybe_matches_alt_index() { using pattern_t = std::decay_t; @@ -382,17 +421,22 @@ namespace ptn::core::common { return true; } else if constexpr (is_guarded_pattern_v) { - using inner_t = typename guarded_inner_pattern::type; - return variant_pattern_maybe_matches_alt_index(); + using inner_t = typename guarded_inner_pattern< + pattern_t>::type; + return variant_pattern_maybe_matches_alt_index< + inner_t, + Subject, + ActiveIndex>(); } else if constexpr (is_variant_type_is_pattern_v) { - static_assert_variant_alt_unique(); - return ActiveIndex == pattern_t::template alt_index(); + static_assert_variant_alt_unique(); + return ActiveIndex + == pattern_t::template alt_index(); } else if constexpr (is_variant_type_alt_pattern_v) { - constexpr std::size_t I = variant_type_alt_index::value; + constexpr std::size_t + I = variant_type_alt_index::value; static_assert_variant_alt_index(); return ActiveIndex == I; } @@ -405,23 +449,25 @@ namespace ptn::core::common { typename CasesTuple, std::size_t ActiveIndex, std::size_t I = 0, - std::size_t N = - std::tuple_size_v>> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct variant_simple_case_index_for_alt { using tuple_t = std::remove_reference_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; - static constexpr std::size_t value = - simple_variant_pattern_matches_alt_index() - ? I - : variant_simple_case_index_for_alt::value; + static constexpr std::size_t + value = simple_variant_pattern_matches_alt_index< + pattern_t, + Subject, + ActiveIndex>() + ? I + : variant_simple_case_index_for_alt< + Subject, + CasesTuple, + ActiveIndex, + I + 1, + N>::value; }; template - constexpr auto - make_variant_simple_case_index_table(std::index_sequence) { - return std::array{ - static_cast( - variant_simple_case_index_for_alt::value)...}; + constexpr auto make_variant_simple_case_index_table( + std::index_sequence) { + return std::array{static_cast( + variant_simple_case_index_for_alt::value)...}; } template @@ -456,26 +502,25 @@ namespace ptn::core::common { std::conditional_t< (CaseCount <= std::numeric_limits::max()), std::uint16_t, - std::conditional_t< - (CaseCount <= std::numeric_limits::max()), - std::uint32_t, - std::size_t>>>; + std::conditional_t<(CaseCount <= std::numeric_limits< + std::uint32_t>::max()), + std::uint32_t, + std::size_t>>>; template < typename Subject, typename CasesTuple, - typename SubjectValue = - std::remove_cv_t>, + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>, std::size_t AltCount = std::variant_size_v> struct variant_simple_dispatch_metadata { static constexpr std::size_t case_count = std::tuple_size_v< std::remove_reference_t>; using case_index_t = variant_case_index_t; - static_assert( - case_count - <= static_cast( - std::numeric_limits::max()), - "variant simple dispatch case index exceeds compact type range"); + static_assert(case_count <= static_cast( + std::numeric_limits::max()), + "variant simple dispatch case index exceeds " + "compact type range"); alignas(64) static constexpr auto case_index_table = make_variant_simple_case_index_table>> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct variant_typed_case_first_match_index_for_alt { using tuple_t = std::remove_reference_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; - static constexpr std::size_t value = - variant_pattern_maybe_matches_alt_index() - ? I - : variant_typed_case_first_match_index_for_alt::value; + static constexpr std::size_t + value = variant_pattern_maybe_matches_alt_index< + pattern_t, + Subject, + ActiveIndex>() + ? I + : variant_typed_case_first_match_index_for_alt< + Subject, + CasesTuple, + ActiveIndex, + I + 1, + N>::value; }; template >> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct variant_typed_case_last_match_index_exclusive_for_alt { using tuple_t = std::remove_reference_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; static constexpr std::size_t tail = - variant_typed_case_last_match_index_exclusive_for_alt::value; - - static constexpr std::size_t value = - variant_pattern_maybe_matches_alt_index() - ? (tail > (I + 1) ? tail : (I + 1)) - : tail; + variant_typed_case_last_match_index_exclusive_for_alt< + Subject, + CasesTuple, + ActiveIndex, + I + 1, + N>::value; + + static constexpr std::size_t + value = variant_pattern_maybe_matches_alt_index< + pattern_t, + Subject, + ActiveIndex>() + ? (tail > (I + 1) ? tail : (I + 1)) + : tail; }; template - struct variant_typed_case_last_match_index_exclusive_for_alt { + struct variant_typed_case_last_match_index_exclusive_for_alt< + Subject, + CasesTuple, + ActiveIndex, + N, + N> { static constexpr std::size_t value = 0; }; template >> + std::size_t CaseCount = std::tuple_size_v< + std::remove_reference_t>> struct variant_typed_case_range_for_alt { - static constexpr std::size_t begin = - variant_typed_case_first_match_index_for_alt::value; + static constexpr std::size_t + begin = variant_typed_case_first_match_index_for_alt< + Subject, + CasesTuple, + ActiveIndex>::value; static constexpr std::size_t end = - variant_typed_case_last_match_index_exclusive_for_alt::value; + variant_typed_case_last_match_index_exclusive_for_alt< + Subject, + CasesTuple, + ActiveIndex>::value; - static constexpr bool has_any = begin < end && begin < CaseCount; + static constexpr bool has_any = begin < end + && begin < CaseCount; }; // Stores the residual case slice for one variant alternative. - // A bucketed variant plan first routes by `variant.index()`, then replays - // only this `[begin, end)` slice to preserve first-match semantics. + // A bucketed variant plan first routes by `variant.index()`, + // then replays only this `[begin, end)` slice to preserve + // first-match semantics. struct variant_typed_case_bucket { std::size_t begin; std::size_t end; @@ -594,43 +648,41 @@ namespace ptn::core::common { using bucket_t = variant_typed_case_bucket; // Materializes every per-alt residual slice once. - // The dispatch plan reuses this table instead of recomputing the same - // range traits at each dispatch site. - return std::array{ - bucket_t{ - variant_typed_case_range_for_alt::begin, - variant_typed_case_range_for_alt::end, - variant_typed_case_range_for_alt::has_any}...}; + // The dispatch plan reuses this table instead of recomputing + // the same range traits at each dispatch site. + return std::array{bucket_t{ + variant_typed_case_range_for_alt::begin, + variant_typed_case_range_for_alt::end, + variant_typed_case_range_for_alt::has_any}...}; } template using variant_compact_alt_index_t = std::conditional_t< (UsedAltCount <= std::numeric_limits::max()), std::uint8_t, - std::conditional_t< - (UsedAltCount <= std::numeric_limits::max()), - std::uint16_t, - std::uint32_t>>; + std::conditional_t<(UsedAltCount <= std::numeric_limits< + std::uint16_t>::max()), + std::uint16_t, + std::uint32_t>>; template constexpr std::size_t count_typed_variant_compact_dispatch_alts( const BucketTable &bucket_table, std::index_sequence) { - // Counts only alternatives that keep a non-empty residual slice. - // Empty alternatives fall straight to the default path. - return (std::size_t{0} - + ... + // Counts only alternatives that keep a non-empty residual + // slice. Empty alternatives fall straight to the default path. + return (std::size_t{0} + ... + (bucket_table[AltIndex].has_any ? std::size_t{1} - : std::size_t{0})); + : std::size_t{0})); } template ) { std::array map{}; - constexpr CompactIndex invalid = std::numeric_limits::max(); + constexpr CompactIndex + invalid = std::numeric_limits::max(); for (auto &entry : map) { entry = invalid; } CompactIndex next = 0; - // Assigns dense cold-path slots only to alternatives that still own a - // residual slice after planning. + // Assigns dense cold-path slots only to alternatives that + // still own a residual slice after planning. (void) std::initializer_list{ (bucket_table[AltIndex].has_any ? (map[AltIndex] = next++, 0) @@ -658,7 +711,9 @@ namespace ptn::core::common { return map; } - template + template constexpr auto make_identity_variant_compact_alt_index_map( std::index_sequence) { return std::array{ @@ -668,33 +723,37 @@ namespace ptn::core::common { template < typename Subject, typename CasesTuple, - typename SubjectValue = - std::remove_cv_t>, + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>, std::size_t AltCount = std::variant_size_v> struct typed_variant_dispatch_metadata { - static constexpr std::size_t case_count = - std::tuple_size_v>; + static constexpr std::size_t case_count = std::tuple_size_v< + std::remove_reference_t>; using case_bucket_t = variant_typed_case_bucket; - // Maps each full variant index to the residual case slice that must be - // replayed after that alternative wins the primary dispatch. - static constexpr auto case_bucket_table = - make_typed_variant_case_bucket_table( - std::make_index_sequence{}); + // Maps each full variant index to the residual case slice that + // must be replayed after that alternative wins the primary + // dispatch. + static constexpr auto + case_bucket_table = make_typed_variant_case_bucket_table< + Subject, + CasesTuple, + case_count>(std::make_index_sequence{}); - static constexpr std::size_t used_alt_count = - count_typed_variant_compact_dispatch_alts( + static constexpr std::size_t + used_alt_count = count_typed_variant_compact_dispatch_alts( case_bucket_table, std::make_index_sequence{}); - using compact_index_t = variant_compact_alt_index_t; - static constexpr compact_index_t k_invalid_compact_index = - std::numeric_limits::max(); + using compact_index_t = variant_compact_alt_index_t< + used_alt_count>; + static constexpr compact_index_t + k_invalid_compact_index = std::numeric_limits< + compact_index_t>::max(); - // Maps a full variant index to the compact cold-path trampoline table. - // Alternatives with no residual slice keep the invalid sentinel. + // Maps a full variant index to the compact cold-path + // trampoline table. Alternatives with no residual slice keep + // the invalid sentinel. static constexpr auto compact_alt_index_map = make_typed_variant_compact_alt_index_map( @@ -707,7 +766,8 @@ namespace ptn::core::common { using case_t = std::remove_reference_t; using pattern_t = traits::case_pattern_t; using handler_t = traits::case_handler_t; - using bound_args_t = pat::base::binding_args_t; + using bound_args_t = pat::base::binding_args_t; static constexpr bool value = is_simple_variant_dispatch_pattern_v @@ -720,7 +780,8 @@ namespace ptn::core::common { using case_t = std::remove_reference_t; using pattern_t = traits::case_pattern_t; using handler_t = traits::case_handler_t; - using bound_args_t = pat::base::binding_args_t; + using bound_args_t = pat::base::binding_args_t; static constexpr bool literal_subject_compatible = is_simple_literal_subject_compatible< @@ -728,10 +789,11 @@ namespace ptn::core::common { Subject, is_simple_literal_pattern_v>::value; - static constexpr bool value = - (is_wildcard_pattern_v || literal_subject_compatible) - && std::tuple_size_v == 0 - && std::is_invocable_v; + static constexpr bool + value = (is_wildcard_pattern_v + || literal_subject_compatible) + && std::tuple_size_v == 0 + && std::is_invocable_v; }; template @@ -739,7 +801,8 @@ namespace ptn::core::common { using case_t = std::remove_reference_t; using pattern_t = traits::case_pattern_t; using handler_t = traits::case_handler_t; - using bound_args_t = pat::base::binding_args_t; + using bound_args_t = pat::base::binding_args_t; static constexpr bool literal_subject_compatible = is_static_literal_subject_compatible< @@ -747,14 +810,16 @@ namespace ptn::core::common { Subject, is_static_literal_pattern_v>::value; - static constexpr bool value = - (is_wildcard_pattern_v || literal_subject_compatible) - && std::tuple_size_v == 0 - && std::is_invocable_v; + static constexpr bool + value = (is_wildcard_pattern_v + || literal_subject_compatible) + && std::tuple_size_v == 0 + && std::is_invocable_v; }; - // Describes the primary discriminator a single case contributes to the IR. - // This is the key the planner may use before any residual checking. + // Describes the primary discriminator a single case contributes + // to the IR. This is the key the planner may use before any + // residual checking. enum class case_discriminator_kind { opaque, wildcard, @@ -763,17 +828,19 @@ namespace ptn::core::common { variant_alt }; - // Describes the work that still remains after primary-key routing. - // `guard` means the key is usable, but the bucket must still evaluate a - // guard. `structural` means the bucket must re-enter general matching. + // Describes the work that still remains after primary-key + // routing. `guard` means the key is usable, but the bucket must + // still evaluate a guard. `structural` means the bucket must + // re-enter general matching. enum class case_residual_kind { none, guard, structural }; - // Describes how much the case's binding behavior constrains lowering. - // `general` means the planner must assume full matcher replay semantics. + // Describes how much the case's binding behavior constrains + // lowering. `general` means the planner must assume full matcher + // replay semantics. enum class case_binding_kind { none, direct_ref, @@ -793,78 +860,102 @@ namespace ptn::core::common { }; template - using unguarded_pattern_t = typename unguarded_pattern::type; + using unguarded_pattern_t = typename unguarded_pattern< + Pattern>::type; // Analyzes one case into lowering-friendly IR. - // This is the seam between the pattern DSL and the planner: new pattern - // kinds should first describe their discriminator, residual work, and - // binding shape here before any concrete dispatch plan is chosen. + // This is the seam between the pattern DSL and the planner: new + // pattern kinds should first describe their discriminator, + // residual work, and binding shape here before any concrete + // dispatch plan is chosen. template struct case_analysis { - using case_t = std::remove_reference_t; - using pattern_t = traits::case_pattern_t; - // Strips a guard wrapper so discriminator analysis sees the underlying - // pattern shape rather than the guard adapter itself. - using core_pattern_t = unguarded_pattern_t; - using handler_t = traits::case_handler_t; + using case_t = std::remove_reference_t; + using pattern_t = traits::case_pattern_t; + // Strips a guard wrapper so discriminator analysis sees the + // underlying pattern shape rather than the guard adapter + // itself. + using core_pattern_t = unguarded_pattern_t; + using handler_t = traits::case_handler_t; // Captures the values this case would bind into the handler. - using bound_args_t = pat::base::binding_args_t; - using subject_t = std::remove_cv_t>; - - // Guards never block key extraction by themselves, but they always force - // residual work after the primary dispatch key hits. - static constexpr bool is_guarded = is_guarded_pattern_v; - // Any non-empty binding tuple means this case changes handler inputs. - static constexpr bool has_bindings = - std::tuple_size_v != 0; - static constexpr bool is_wildcard = - is_wildcard_pattern_v; + using bound_args_t = pat::base::binding_args_t; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; + + // Guards never block key extraction by themselves, but they + // always force residual work after the primary dispatch key + // hits. + static constexpr bool + is_guarded = is_guarded_pattern_v; + // Any non-empty binding tuple means this case changes handler + // inputs. + static constexpr bool + has_bindings = std::tuple_size_v != 0; + static constexpr bool + is_wildcard = is_wildcard_pattern_v; // Selects the primary key this case exposes to the planner. - // The order matters: wildcard takes precedence over any inner shape, - // then static literals, then runtime literals, then variant alt keys. - // Anything else is opaque to the current planner. + // The order matters: wildcard takes precedence over any inner + // shape, then static literals, then runtime literals, then + // variant alt keys. Anything else is opaque to the current + // planner. static constexpr case_discriminator_kind discriminator_kind = is_wildcard ? case_discriminator_kind::wildcard : (is_static_literal_pattern_v ? case_discriminator_kind::static_literal : (is_simple_literal_pattern_v - ? case_discriminator_kind::runtime_literal - : ((is_variant_type_is_pattern_v - || is_variant_type_alt_pattern_v) - ? case_discriminator_kind::variant_alt - : case_discriminator_kind::opaque))); + ? case_discriminator_kind:: + runtime_literal + : ((is_variant_type_is_pattern_v< + core_pattern_t> + || is_variant_type_alt_pattern_v< + core_pattern_t>) + ? case_discriminator_kind:: + variant_alt + : case_discriminator_kind:: + opaque))); // Records whether binding can stay on a direct-ref fast path. - // `direct_ref` is the only binding form the current variant lowering can - // keep without replaying the full matcher. - static constexpr case_binding_kind binding_kind = - !has_bindings - ? case_binding_kind::none - : (is_variant_direct_ref_bind_pattern_v - ? case_binding_kind::direct_ref - : case_binding_kind::general); + // `direct_ref` is the only binding form the current variant + // lowering can keep without replaying the full matcher. + static constexpr case_binding_kind + binding_kind = !has_bindings + ? case_binding_kind::none + : (is_variant_direct_ref_bind_pattern_v< + core_pattern_t> + ? case_binding_kind::direct_ref + : case_binding_kind::general); // Records what must still happen after primary-key routing. // The residual ordering is also intentional: // - A guard always becomes a residual guard check. - // - A non-simple variant case still has a usable variant-alt key, but + // - A non-simple variant case still has a usable variant-alt + // key, but // must replay structural matching inside that alt bucket. - // - Opaque discriminators and general bindings force structural replay. - // - Everything else is fully handled by primary dispatch alone. + // - Opaque discriminators and general bindings force + // structural replay. + // - Everything else is fully handled by primary dispatch + // alone. static constexpr case_residual_kind residual_kind = is_guarded ? case_residual_kind::guard - : ((discriminator_kind == case_discriminator_kind::variant_alt - && !is_simple_variant_dispatch_case::value) - || discriminator_kind == case_discriminator_kind::opaque - || binding_kind == case_binding_kind::general + : ((discriminator_kind + == case_discriminator_kind::variant_alt + && !is_simple_variant_dispatch_case< + case_t, + Subject>::value) + || discriminator_kind + == case_discriminator_kind::opaque + || binding_kind + == case_binding_kind::general ? case_residual_kind::structural : case_residual_kind::none); - // These flags answer a narrower question than `discriminator_kind`: - // they say whether the whole case satisfies one concrete plan family. + // These flags answer a narrower question than + // `discriminator_kind`: they say whether the whole case + // satisfies one concrete plan family. static constexpr bool supports_simple_literal_dispatch = is_simple_literal_dispatch_case::value; static constexpr bool supports_static_literal_dispatch = @@ -875,13 +966,16 @@ namespace ptn::core::common { // Lowered dispatch is enabled only when every case qualifies. template - struct is_simple_variant_dispatch_cases_tuple : std::false_type {}; + struct is_simple_variant_dispatch_cases_tuple : std::false_type { + }; template - struct is_simple_variant_dispatch_cases_tuple> + struct is_simple_variant_dispatch_cases_tuple< + Subject, + std::tuple> : std::bool_constant<( - is_simple_variant_dispatch_case::value && ...)> { - }; + is_simple_variant_dispatch_case::value + && ...)> {}; template constexpr bool is_simple_variant_dispatch_cases_tuple_v = @@ -890,13 +984,16 @@ namespace ptn::core::common { std::remove_reference_t>::value; template - struct is_simple_literal_dispatch_cases_tuple : std::false_type {}; + struct is_simple_literal_dispatch_cases_tuple : std::false_type { + }; template - struct is_simple_literal_dispatch_cases_tuple> + struct is_simple_literal_dispatch_cases_tuple< + Subject, + std::tuple> : std::bool_constant<( - is_simple_literal_dispatch_case::value && ...)> { - }; + is_simple_literal_dispatch_case::value + && ...)> {}; template constexpr bool is_simple_literal_dispatch_cases_tuple_v = @@ -907,39 +1004,47 @@ namespace ptn::core::common { template >> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct is_static_literal_dispatch_sequence { using tuple_t = std::remove_reference_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; - // Every case in the sequence must satisfy the static-literal case rules. - static constexpr bool current_case_ok = - is_static_literal_dispatch_case::value; + // Every case in the sequence must satisfy the static-literal + // case rules. + static constexpr bool + current_case_ok = is_static_literal_dispatch_case< + case_t, + Subject>::value; // A wildcard is accepted only as the final default case. - static constexpr bool value = - current_case_ok - && (is_wildcard_pattern_v - ? (I + 1 == N) - : is_static_literal_dispatch_sequence::value); + static constexpr bool + value = current_case_ok + && (is_wildcard_pattern_v + ? (I + 1 == N) + : is_static_literal_dispatch_sequence< + Subject, + CasesTuple, + I + 1, + N>::value); }; template struct is_static_literal_dispatch_sequence : std::true_type {}; + N> : std::true_type { + }; template - struct is_static_literal_dispatch_cases_tuple : std::false_type {}; + struct is_static_literal_dispatch_cases_tuple : std::false_type { + }; template - struct is_static_literal_dispatch_cases_tuple> + struct is_static_literal_dispatch_cases_tuple< + Subject, + std::tuple> : std::bool_constant>::value> {}; @@ -953,19 +1058,24 @@ namespace ptn::core::common { template >> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct static_literal_case_count { using tuple_t = std::remove_reference_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; - // Counts only actual static-literal cases. Wildcards contribute to the - // default slot, but not to the dense literal table density. - static constexpr std::size_t value = - (is_static_literal_pattern_v ? std::size_t{1} - : std::size_t{0}) - + static_literal_case_count::value; + // Counts only actual static-literal cases. Wildcards + // contribute to the default slot, but not to the dense literal + // table density. + static constexpr std::size_t + value = (is_static_literal_pattern_v + ? std::size_t{1} + : std::size_t{0}) + + static_literal_case_count::value; }; template @@ -993,15 +1103,15 @@ namespace ptn::core::common { template >> + std::size_t N = std::tuple_size_v< + std::remove_reference_t>> struct static_literal_value_range; template + bool IsLiteral> struct static_literal_value_range_case; template { using tuple_t = std::remove_reference_t; - using subject_t = std::remove_cv_t>; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; using key_t = literal_subject_key_t; using case_t = std::tuple_element_t; using pattern_t = traits::case_pattern_t; - using tail_t = static_literal_value_range; - - // Folds the current static literal into the tail min/max summary. - static constexpr key_t current_value = - static_literal_case_value::value; + using tail_t = static_literal_value_range; + + // Folds the current static literal into the tail min/max + // summary. + static constexpr key_t + current_value = static_literal_case_value::value; static constexpr bool has_any = true; - static constexpr key_t min = - tail_t::has_any - ? (current_value < tail_t::min ? current_value : tail_t::min) - : current_value; - - static constexpr key_t max = - tail_t::has_any - ? (current_value > tail_t::max ? current_value : tail_t::max) - : current_value; + static constexpr key_t min = tail_t::has_any + ? (current_value < tail_t::min + ? current_value + : tail_t::min) + : current_value; + + static constexpr key_t max = tail_t::has_any + ? (current_value > tail_t::max + ? current_value + : tail_t::max) + : current_value; }; template ; - using key_t = typename tail_t::key_t; + using key_t = typename tail_t::key_t; // Non-literal cases do not affect the dense-table key range. - static constexpr bool has_any = tail_t::has_any; - static constexpr key_t min = tail_t::min; - static constexpr key_t max = tail_t::max; + static constexpr bool has_any = tail_t::has_any; + static constexpr key_t min = tail_t::min; + static constexpr key_t max = tail_t::max; }; template >>>> { - }; + is_static_literal_pattern_v< + traits::case_pattern_t>>>> {}; template struct static_literal_value_range { - using subject_t = std::remove_cv_t>; - using key_t = literal_subject_key_t; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; + using key_t = literal_subject_key_t; - static constexpr bool has_any = false; - static constexpr key_t min = key_t{}; - static constexpr key_t max = key_t{}; + static constexpr bool has_any = false; + static constexpr key_t min = key_t{}; + static constexpr key_t max = key_t{}; }; template struct tuple_last_case_is_wildcard - : std::bool_constant>>> {}; + : std::bool_constant< + is_wildcard_pattern_v>>> {}; template - struct tuple_last_case_is_wildcard : std::false_type {}; + struct tuple_last_case_is_wildcard : std::false_type { + }; template >> + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>> struct static_literal_dispatch_metadata { using tuple_t = std::remove_reference_t; using key_t = literal_subject_key_t; - // `case_count` includes the trailing wildcard default if present. - static constexpr std::size_t case_count = - std::tuple_size_v; + // `case_count` includes the trailing wildcard default if + // present. + static constexpr std::size_t + case_count = std::tuple_size_v; // `literal_case_count` counts only keyed literal entries. - static constexpr std::size_t literal_case_count = - static_literal_case_count::value; + static constexpr std::size_t + literal_case_count = static_literal_case_count< + Subject, + CasesTuple>::value; using case_index_t = variant_case_index_t; - using value_range_t = - static_literal_value_range; + using value_range_t = static_literal_value_range; // These values describe the dense candidate key span. - static constexpr bool has_literal_cases = value_range_t::has_any; - static constexpr key_t min_value = value_range_t::min; - static constexpr key_t max_value = value_range_t::max; - static constexpr std::size_t range_size = - has_literal_cases - ? static_literal_range_width(min_value, max_value) - : 0u; - - static constexpr case_index_t k_invalid_case_index = - std::numeric_limits::max(); - - // The final wildcard, when present, is reused as the dense dispatch - // default slot. - static constexpr bool has_default_case = - tuple_last_case_is_wildcard::value; - - static constexpr case_index_t default_case_index = - has_default_case ? static_cast(case_count - 1) - : k_invalid_case_index; - - // Dense dispatch is worthwhile only when the key span stays small enough - // and dense enough relative to the number of literal cases. + static constexpr bool + has_literal_cases = value_range_t::has_any; + static constexpr key_t min_value = value_range_t::min; + static constexpr key_t max_value = value_range_t::max; + static constexpr std::size_t + range_size = has_literal_cases + ? static_literal_range_width(min_value, + max_value) + : 0u; + + static constexpr case_index_t k_invalid_case_index = std:: + numeric_limits::max(); + + // The final wildcard, when present, is reused as the dense + // dispatch default slot. + static constexpr bool + has_default_case = tuple_last_case_is_wildcard< + tuple_t, + case_count>::value; + + static constexpr case_index_t + default_case_index = has_default_case + ? static_cast( + case_count - 1) + : k_invalid_case_index; + + // Dense dispatch is worthwhile only when the key span stays + // small enough and dense enough relative to the number of + // literal cases. static constexpr bool use_dense_table = - has_literal_cases - && literal_case_count > 0 + has_literal_cases && literal_case_count > 0 && case_count <= k_static_literal_dense_dispatch_max_cases - && range_size <= k_static_literal_dense_dispatch_max_span - && range_size + &&range_size + <= k_static_literal_dense_dispatch_max_span + &&range_size <= literal_case_count - * k_static_literal_dense_dispatch_max_density; + *k_static_literal_dense_dispatch_max_density; - // Runtime dense dispatch relaxes the density heuristic for cases - // where the span is still manageable but the static density check - // would reject a worthwhile O(1) table. + // Runtime dense dispatch relaxes the density heuristic for + // cases where the span is still manageable but the static + // density check would reject a worthwhile O(1) table. static constexpr bool use_runtime_dense_table = has_literal_cases && literal_case_count @@ -1159,37 +1287,42 @@ namespace ptn::core::common { }; template - struct is_static_literal_dense_dispatch_enabled : std::false_type {}; + struct is_static_literal_dense_dispatch_enabled + : std::false_type {}; template - struct is_static_literal_dense_dispatch_enabled - : std::bool_constant< - static_literal_dispatch_metadata:: - use_dense_table> {}; + struct is_static_literal_dense_dispatch_enabled + : std::bool_constant::use_dense_table> {}; template - struct is_runtime_literal_dense_dispatch_enabled : std::false_type {}; + struct is_runtime_literal_dense_dispatch_enabled + : std::false_type {}; template struct is_runtime_literal_dense_dispatch_enabled - : std::bool_constant< - static_literal_dispatch_metadata:: - use_runtime_dense_table> {}; + CasesTuple, + true> + : std::bool_constant::use_runtime_dense_table> {}; // Summarizes the three lowering rules discussed for the engine. - // `full` means direct keyed dispatch is legal, `bucketed` means keyed - // dispatch may narrow the search before replay, and `none` means the - // planner must fall back to sequential evaluation. + // `full` means direct keyed dispatch is legal, `bucketed` means + // keyed dispatch may narrow the search before replay, and `none` + // means the planner must fall back to sequential evaluation. enum class lowering_legality { none, bucketed, full }; - // Names the concrete runtime shape selected after legality analysis. - // Multiple plan kinds may share the same legality grade. + // Names the concrete runtime shape selected after legality + // analysis. Multiple plan kinds may share the same legality + // grade. enum class dispatch_plan_kind { sequential, literal_linear, @@ -1204,81 +1337,97 @@ namespace ptn::core::common { template struct case_sequence_ir> { - using subject_t = std::remove_cv_t>; - // Retains per-case IR so future planners can inspect the whole sequence - // without re-running case-level trait analysis. - using analyses_t = std::tuple...>; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; + // Retains per-case IR so future planners can inspect the whole + // sequence without re-running case-level trait analysis. + using analyses_t = std::tuple< + case_analysis...>; static constexpr std::size_t case_count = sizeof...(Cases); - // `true` if any case still needs work after primary-key routing. - static constexpr bool has_residual_cases = - ((case_analysis::residual_kind - != case_residual_kind::none) - || ... || false); + // `true` if any case still needs work after primary-key + // routing. + static constexpr bool + has_residual_cases = ((case_analysis:: + residual_kind + != case_residual_kind::none) + || ... || false); - // `true` if any case binds in a way that blocks the most aggressive - // lowering shapes. - static constexpr bool has_binding_constraints = - ((case_analysis::binding_kind - != case_binding_kind::none) - || ... || false); + // `true` if any case binds in a way that blocks the most + // aggressive lowering shapes. + static constexpr bool + has_binding_constraints = ((case_analysis:: + binding_kind + != case_binding_kind::none) + || ... || false); - // `true` if at least one case exposes no planner-visible discriminator. + // `true` if at least one case exposes no planner-visible + // discriminator. static constexpr bool has_opaque_discriminator = ((case_analysis::discriminator_kind == case_discriminator_kind::opaque) || ... || false); - // Rule 1: every case participates in dense static-literal dispatch. - // This is stronger than "all cases are static literals": the resulting - // literal span must also satisfy the dense-table heuristics. + // Rule 1: every case participates in dense static-literal + // dispatch. This is stronger than "all cases are static + // literals": the resulting literal span must also satisfy the + // dense-table heuristics. static constexpr bool can_use_static_literal_dispatch = is_static_literal_dense_dispatch_enabled< subject_t, std::tuple, - ((std::is_integral_v || std::is_enum_v) + ((std::is_integral_v + || std::is_enum_v) && (case_analysis:: supports_static_literal_dispatch && ...))>::value; - // Rule 1: literal cases that satisfy the relaxed density heuristic - // can use O(1) array-based dispatch even when the stricter static - // dense check fails. + // Rule 1: literal cases that satisfy the relaxed density + // heuristic can use O(1) array-based dispatch even when the + // stricter static dense check fails. static constexpr bool can_use_runtime_literal_dense_dispatch = is_runtime_literal_dense_dispatch_enabled< subject_t, std::tuple, - ((std::is_integral_v || std::is_enum_v) + ((std::is_integral_v + || std::is_enum_v) && (case_analysis:: supports_static_literal_dispatch && ...))>::value; - // Rule 1: every case participates in direct runtime literal dispatch. - // This is the older linear literal lowering. It stays available when - // dense static-literal dispatch is not legal or not profitable. + // Rule 1: every case participates in direct runtime literal + // dispatch. This is the older linear literal lowering. It + // stays available when dense static-literal dispatch is not + // legal or not profitable. static constexpr bool can_use_simple_literal_dispatch = - (std::is_integral_v || std::is_enum_v) - && (case_analysis::supports_simple_literal_dispatch + (std::is_integral_v + || std::is_enum_v) + && (case_analysis:: + supports_simple_literal_dispatch && ...); - // Rule 1: every case can dispatch directly from the active variant alt. - // No residual replay is needed once the active alt is known. + // Rule 1: every case can dispatch directly from the active + // variant alt. No residual replay is needed once the active + // alt is known. static constexpr bool can_use_simple_variant_dispatch = is_variant_subject_v - && (case_analysis::supports_variant_direct_dispatch + && (case_analysis:: + supports_variant_direct_dispatch && ...); - // Rule 2: the variant alt is still a usable primary key even if some - // cases need residual replay within the selected bucket. - // Today this is the only bucketed lowering family. The planner will - // still reject non-variant subjects here. - static constexpr bool can_use_variant_alt_dispatch = - is_variant_subject_v; + // Rule 2: the variant alt is still a usable primary key even + // if some cases need residual replay within the selected + // bucket. Today this is the only bucketed lowering family. The + // planner will still reject non-variant subjects here. + static constexpr bool + can_use_variant_alt_dispatch = is_variant_subject_v< + subject_t>; // Collapses the aggregate IR back to the three lowering rules. // Precedence matters: - // - `full` wins whenever a direct literal or direct variant plan exists. + // - `full` wins whenever a direct literal or direct variant + // plan exists. // - Otherwise a variant subject may still use `bucketed`. // - Everything else falls back to sequential evaluation. static constexpr lowering_legality legality = @@ -1287,222 +1436,251 @@ namespace ptn::core::common { || can_use_simple_literal_dispatch || can_use_simple_variant_dispatch ? lowering_legality::full - : (can_use_variant_alt_dispatch ? lowering_legality::bucketed - : lowering_legality::none); + : (can_use_variant_alt_dispatch + ? lowering_legality::bucketed + : lowering_legality::none); }; // Wraps the sequence IR in the planner-facing analysis API. - // This stays declarative so future planners can reuse the same legality - // checks without depending on evaluator internals. + // This stays declarative so future planners can reuse the same + // legality checks without depending on evaluator internals. template struct lowering_analysis { - using ir_t = case_sequence_ir>; + using ir_t = case_sequence_ir< + Subject, + std::remove_reference_t>; - // Re-exports the sequence IR in the shape expected by plan selection. - static constexpr bool can_use_static_literal_dispatch = - ir_t::can_use_static_literal_dispatch; - static constexpr bool can_use_runtime_literal_dense_dispatch = - ir_t::can_use_runtime_literal_dense_dispatch; - static constexpr bool can_use_simple_literal_dispatch = - ir_t::can_use_simple_literal_dispatch; - static constexpr bool can_use_simple_variant_dispatch = - ir_t::can_use_simple_variant_dispatch; - static constexpr bool can_use_variant_alt_dispatch = - ir_t::can_use_variant_alt_dispatch; + // Re-exports the sequence IR in the shape expected by plan + // selection. + static constexpr bool can_use_static_literal_dispatch = ir_t:: + can_use_static_literal_dispatch; + static constexpr bool + can_use_runtime_literal_dense_dispatch = ir_t:: + can_use_runtime_literal_dense_dispatch; + static constexpr bool can_use_simple_literal_dispatch = ir_t:: + can_use_simple_literal_dispatch; + static constexpr bool can_use_simple_variant_dispatch = ir_t:: + can_use_simple_variant_dispatch; + static constexpr bool can_use_variant_alt_dispatch = ir_t:: + can_use_variant_alt_dispatch; static constexpr lowering_legality legality = ir_t::legality; }; template struct sequential_dispatch_plan { - using subject_t = std::remove_cv_t>; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; using cases_tuple_t = std::remove_reference_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::sequential; - // Rule 3: no keyed lowering is legal, so evaluator replays all cases. - static constexpr lowering_legality legality = - lowering_legality::none; + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::sequential; + // Rule 3: no keyed lowering is legal, so evaluator replays all + // cases. + static constexpr lowering_legality + legality = lowering_legality::none; }; template struct literal_linear_dispatch_plan { - using subject_t = std::remove_cv_t>; + using subject_t = std::remove_cv_t< + std::remove_reference_t>; using cases_tuple_t = std::remove_reference_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::literal_linear; + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::literal_linear; // Rule 1: each case is already a direct literal dispatch case. - static constexpr lowering_legality legality = - lowering_legality::full; + static constexpr lowering_legality + legality = lowering_legality::full; }; template >> + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>> struct static_literal_dense_dispatch_plan { using subject_t = SubjectValue; using cases_tuple_t = std::remove_reference_t; - using metadata_t = - static_literal_dispatch_metadata; + using metadata_t = static_literal_dispatch_metadata< + Subject, + CasesTuple, + SubjectValue>; using key_t = typename metadata_t::key_t; using case_index_t = typename metadata_t::case_index_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::static_literal_dense; - static constexpr lowering_legality legality = - lowering_legality::full; - - // Re-exports the dense-table metadata the evaluator needs at runtime. - static constexpr std::size_t case_count = - metadata_t::case_count; - static constexpr std::size_t literal_case_count = - metadata_t::literal_case_count; - static constexpr std::size_t range_size = - metadata_t::range_size; - static constexpr bool has_default_case = - metadata_t::has_default_case; + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::static_literal_dense; + static constexpr lowering_legality + legality = lowering_legality::full; + + // Re-exports the dense-table metadata the evaluator needs at + // runtime. + static constexpr std::size_t + case_count = metadata_t::case_count; + static constexpr std::size_t + literal_case_count = metadata_t::literal_case_count; + static constexpr std::size_t + range_size = metadata_t::range_size; + static constexpr bool + has_default_case = metadata_t::has_default_case; static constexpr key_t min_value = metadata_t::min_value; static constexpr key_t max_value = metadata_t::max_value; - static constexpr case_index_t default_case_index = - metadata_t::default_case_index; - static constexpr case_index_t k_invalid_case_index = - metadata_t::k_invalid_case_index; + static constexpr case_index_t + default_case_index = metadata_t::default_case_index; + static constexpr case_index_t + k_invalid_case_index = metadata_t::k_invalid_case_index; }; - template >, - std::size_t AltCount = std::variant_size_v> + template < + typename Subject, + typename CasesTuple, + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>, + std::size_t AltCount = std::variant_size_v> struct variant_simple_dispatch_plan { using subject_t = SubjectValue; using cases_tuple_t = std::remove_reference_t; - using metadata_t = variant_simple_dispatch_metadata; - using case_index_t = typename metadata_t::case_index_t; + using metadata_t = variant_simple_dispatch_metadata< + Subject, + CasesTuple, + SubjectValue, + AltCount>; + using case_index_t = typename metadata_t::case_index_t; using compact_index_t = variant_compact_alt_index_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::variant_simple; - static constexpr lowering_legality legality = - lowering_legality::full; + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::variant_simple; + static constexpr lowering_legality + legality = lowering_legality::full; - // All variant alternatives stay addressable in the simple plan. - static constexpr std::size_t alt_count = AltCount; - static constexpr std::size_t case_count = metadata_t::case_count; + // All variant alternatives stay addressable in the simple + // plan. + static constexpr std::size_t alt_count = AltCount; + static constexpr std::size_t + case_count = metadata_t::case_count; static constexpr std::size_t used_alt_count = AltCount; - static constexpr variant_dispatch_tier tier = - variant_dispatch_tier_for_alt_count(); - // Maps each alternative directly to the terminal case chosen for it. - static constexpr auto case_index_table = - metadata_t::case_index_table; - // The simple plan keeps every alternative, so the compact map is the - // identity function. + static constexpr variant_dispatch_tier + tier = variant_dispatch_tier_for_alt_count(); + // Maps each alternative directly to the terminal case chosen + // for it. + static constexpr auto + case_index_table = metadata_t::case_index_table; + // The simple plan keeps every alternative, so the compact map + // is the identity function. static constexpr auto compact_alt_index_map = - make_identity_variant_compact_alt_index_map( - std::make_index_sequence{}); - static constexpr compact_index_t k_invalid_compact_index = - std::numeric_limits::max(); + make_identity_variant_compact_alt_index_map< + compact_index_t, + AltCount>(std::make_index_sequence{}); + static constexpr compact_index_t + k_invalid_compact_index = std::numeric_limits< + compact_index_t>::max(); template struct case_entry { - static_assert(AltIndex < AltCount, "variant alt index out of range"); - - // `case_index` names the terminal case that handles this alternative. - static constexpr case_index_t case_index = - case_index_table[AltIndex]; - // `false` means this alternative falls through to `otherwise`. + static_assert(AltIndex < AltCount, + "variant alt index out of range"); + + // `case_index` names the terminal case that handles this + // alternative. + static constexpr case_index_t + case_index = case_index_table[AltIndex]; + // `false` means this alternative falls through to + // `otherwise`. static constexpr bool has_any = case_index < case_count; }; }; - template >, - std::size_t AltCount = std::variant_size_v> + template < + typename Subject, + typename CasesTuple, + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>, + std::size_t AltCount = std::variant_size_v> struct variant_alt_bucketed_dispatch_plan { using subject_t = SubjectValue; using cases_tuple_t = std::remove_reference_t; - using metadata_t = typed_variant_dispatch_metadata; + using metadata_t = typed_variant_dispatch_metadata< + Subject, + CasesTuple, + SubjectValue, + AltCount>; using compact_index_t = typename metadata_t::compact_index_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::variant_alt_bucketed; - static constexpr lowering_legality legality = - lowering_legality::bucketed; - - // Only alternatives with a non-empty residual slice stay in the compact - // cold-path table. - static constexpr std::size_t alt_count = AltCount; - static constexpr std::size_t case_count = metadata_t::case_count; - static constexpr std::size_t used_alt_count = metadata_t::used_alt_count; - static constexpr variant_dispatch_tier tier = - variant_dispatch_tier_for_alt_count(); + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::variant_alt_bucketed; + static constexpr lowering_legality + legality = lowering_legality::bucketed; + + // Only alternatives with a non-empty residual slice stay in + // the compact cold-path table. + static constexpr std::size_t alt_count = AltCount; + static constexpr std::size_t + case_count = metadata_t::case_count; + static constexpr std::size_t + used_alt_count = metadata_t::used_alt_count; + static constexpr variant_dispatch_tier + tier = variant_dispatch_tier_for_alt_count(); // The evaluator uses this map only on the cold path. - static constexpr auto compact_alt_index_map = - metadata_t::compact_alt_index_map; - // This table is the real payload of bucketed lowering: each alternative - // maps to the `[begin, end)` slice that must be replayed. - static constexpr auto case_bucket_table = - metadata_t::case_bucket_table; - static constexpr compact_index_t k_invalid_compact_index = - metadata_t::k_invalid_compact_index; + static constexpr auto + compact_alt_index_map = metadata_t::compact_alt_index_map; + // This table is the real payload of bucketed lowering: each + // alternative maps to the `[begin, end)` slice that must be + // replayed. + static constexpr auto + case_bucket_table = metadata_t::case_bucket_table; + static constexpr compact_index_t + k_invalid_compact_index = metadata_t:: + k_invalid_compact_index; template struct case_bucket { - static_assert(AltIndex < AltCount, "variant alt index out of range"); - - // Type-level view for a single precomputed residual case bucket. - // The evaluator replays exactly this half-open interval on a hit. - static constexpr std::size_t begin = - case_bucket_table[AltIndex].begin; - static constexpr std::size_t end = - case_bucket_table[AltIndex].end; - static constexpr bool has_any = - case_bucket_table[AltIndex].has_any; + static_assert(AltIndex < AltCount, + "variant alt index out of range"); + + // Type-level view for a single precomputed residual case + // bucket. The evaluator replays exactly this half-open + // interval on a hit. + static constexpr std::size_t + begin = case_bucket_table[AltIndex].begin; + static constexpr std::size_t + end = case_bucket_table[AltIndex].end; + static constexpr bool has_any = case_bucket_table[AltIndex] + .has_any; }; }; template >> + typename SubjectValue = std::remove_cv_t< + std::remove_reference_t>> struct literal_runtime_dense_dispatch_plan { using subject_t = SubjectValue; using cases_tuple_t = std::remove_reference_t; - using metadata_t = - static_literal_dispatch_metadata; + using metadata_t = static_literal_dispatch_metadata< + Subject, + CasesTuple, + SubjectValue>; using key_t = typename metadata_t::key_t; using case_index_t = typename metadata_t::case_index_t; - static constexpr dispatch_plan_kind kind = - dispatch_plan_kind::literal_runtime_dense; - static constexpr lowering_legality legality = - lowering_legality::full; - - static constexpr std::size_t case_count = - metadata_t::case_count; - static constexpr std::size_t literal_case_count = - metadata_t::literal_case_count; - static constexpr std::size_t range_size = - metadata_t::range_size; - static constexpr bool has_default_case = - metadata_t::has_default_case; + static constexpr dispatch_plan_kind + kind = dispatch_plan_kind::literal_runtime_dense; + static constexpr lowering_legality + legality = lowering_legality::full; + + static constexpr std::size_t + case_count = metadata_t::case_count; + static constexpr std::size_t + literal_case_count = metadata_t::literal_case_count; + static constexpr std::size_t + range_size = metadata_t::range_size; + static constexpr bool + has_default_case = metadata_t::has_default_case; static constexpr key_t min_value = metadata_t::min_value; static constexpr key_t max_value = metadata_t::max_value; - static constexpr case_index_t default_case_index = - metadata_t::default_case_index; - static constexpr case_index_t k_invalid_case_index = - metadata_t::k_invalid_case_index; + static constexpr case_index_t + default_case_index = metadata_t::default_case_index; + static constexpr case_index_t + k_invalid_case_index = metadata_t::k_invalid_case_index; }; template - struct dispatch_plan_for_kind { + struct dispatch_plan_for_kind< + Subject, + CasesTuple, + dispatch_plan_kind::literal_linear> { using type = literal_linear_dispatch_plan; }; template - struct dispatch_plan_for_kind { - using type = static_literal_dense_dispatch_plan; + struct dispatch_plan_for_kind< + Subject, + CasesTuple, + dispatch_plan_kind::static_literal_dense> { + using type = static_literal_dense_dispatch_plan; }; template - struct dispatch_plan_for_kind { - using type = - literal_runtime_dense_dispatch_plan; + struct dispatch_plan_for_kind< + Subject, + CasesTuple, + dispatch_plan_kind::literal_runtime_dense> { + using type = literal_runtime_dense_dispatch_plan; }; template - struct dispatch_plan_for_kind { + struct dispatch_plan_for_kind< + Subject, + CasesTuple, + dispatch_plan_kind::variant_simple> { using type = variant_simple_dispatch_plan; }; template - struct dispatch_plan_for_kind { - using type = - variant_alt_bucketed_dispatch_plan; + struct dispatch_plan_for_kind< + Subject, + CasesTuple, + dispatch_plan_kind::variant_alt_bucketed> { + using type = variant_alt_bucketed_dispatch_plan; }; // Planned dispatch shape for a case sequence. @@ -1567,13 +1751,16 @@ namespace ptn::core::common { ? dispatch_plan_kind::literal_runtime_dense : (analysis_t::can_use_simple_literal_dispatch ? dispatch_plan_kind::literal_linear - : (analysis_t::can_use_simple_variant_dispatch - ? dispatch_plan_kind::variant_simple + : (analysis_t:: + can_use_simple_variant_dispatch + ? dispatch_plan_kind:: + variant_simple : (analysis_t:: can_use_variant_alt_dispatch ? dispatch_plan_kind:: variant_alt_bucketed - : dispatch_plan_kind::sequential)))); + : dispatch_plan_kind:: + sequential)))); using type = typename dispatch_plan_for_kind - using dispatch_plan = - typename dispatch_plan_selector::type; + using dispatch_plan = typename dispatch_plan_selector< + Subject, + CasesTuple>::type; } // namespace detail