diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index c15b420..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: 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/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..8cdf166 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,102 @@ 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 +1615,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 +2010,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 +2114,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 +2200,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_rdense.cpp b/bench/compile-time/ct_bench_lit_rdense.cpp new file mode 100644 index 0000000..a8fbc55 --- /dev/null +++ b/bench/compile-time/ct_bench_lit_rdense.cpp @@ -0,0 +1,31 @@ +#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, ""); +} // namespace diff --git a/include/ptn/core/common/eval.hpp b/include/ptn/core/common/eval.hpp index 801ea39..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,7 +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) { 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..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,25 +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_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, @@ -109,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 { @@ -121,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)(); } @@ -143,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)); } } }; @@ -176,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. @@ -192,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 @@ -204,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 @@ -217,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 = @@ -232,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 @@ -250,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 @@ -261,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>> {}; @@ -276,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 @@ -293,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); }; @@ -304,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 @@ -323,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; } @@ -369,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; @@ -377,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; } @@ -400,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 @@ -451,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; @@ -589,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) @@ -653,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{ @@ -663,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( @@ -702,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 @@ -715,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< @@ -723,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 @@ -734,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< @@ -742,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, @@ -758,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, @@ -788,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 = @@ -870,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 = @@ -885,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 = @@ -902,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> {}; @@ -948,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 @@ -988,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 + <= k_static_literal_dense_dispatch_max_span + &&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_static_literal_dense_dispatch_max_density; + * k_runtime_literal_dense_dispatch_max_density; }; 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 {}; + + template + struct is_runtime_literal_dense_dispatch_enabled + : 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, static_literal_dense, + literal_runtime_dense, variant_simple, variant_alt_bucketed }; @@ -1174,258 +1337,352 @@ 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: 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: 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. 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 = - 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 - : 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_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 >> + 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< + 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 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 @@ -1439,32 +1696,46 @@ namespace ptn::core::common { }; 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 { + 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< + 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. @@ -1476,13 +1747,20 @@ namespace ptn::core::common { static constexpr dispatch_plan_kind kind = analysis_t::can_use_static_literal_dispatch ? dispatch_plan_kind::static_literal_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_variant_alt_dispatch - ? dispatch_plan_kind::variant_alt_bucketed - : dispatch_plan_kind::sequential))); + : (analysis_t::can_use_runtime_literal_dense_dispatch + ? 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_variant_alt_dispatch + ? dispatch_plan_kind:: + variant_alt_bucketed + : 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