_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEEC2Ev:
   71|  18.0k|    DepGraph() noexcept = default;
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE7TxCountEv:
  121|  5.96M|    auto TxCount() const noexcept { return m_used.Count(); }
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE14AddTransactionERK7FeeFrac:
  137|   777k|    {
  138|   777k|        static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
  139|   777k|        auto available = ALL_POSITIONS - m_used;
  140|   777k|        Assume(available.Any());
  ------------------
  |  |  128|   777k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  141|   777k|        DepGraphIndex new_idx = available.First();
  142|   777k|        if (new_idx == entries.size()) {
  ------------------
  |  Branch (142:13): [True: 725k, False: 52.0k]
  ------------------
  143|   725k|            entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
  144|   725k|        } else {
  145|  52.0k|            entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
  146|  52.0k|        }
  147|   777k|        m_used.Set(new_idx);
  148|   777k|        return new_idx;
  149|   777k|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE5EntryC2ERK7FeeFracRKS3_SA_:
   49|   777k|        Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE15AddDependenciesERKS3_j:
  181|  3.68M|    {
  182|  3.68M|        Assume(m_used[child]);
  ------------------
  |  |  128|  3.68M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  183|  3.68M|        Assume(parents.IsSubsetOf(m_used));
  ------------------
  |  |  128|  3.68M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  184|       |        // Compute the ancestors of parents that are not already ancestors of child.
  185|  3.68M|        SetType par_anc;
  186|  3.68M|        for (auto par : parents - Ancestors(child)) {
  ------------------
  |  Branch (186:23): [True: 2.39M, False: 3.68M]
  ------------------
  187|  2.39M|            par_anc |= Ancestors(par);
  188|  2.39M|        }
  189|  3.68M|        par_anc -= Ancestors(child);
  190|       |        // Bail out if there are no such ancestors.
  191|  3.68M|        if (par_anc.None()) return;
  ------------------
  |  Branch (191:13): [True: 1.28M, False: 2.39M]
  ------------------
  192|       |        // To each such ancestor, add as descendants the descendants of the child.
  193|  2.39M|        const auto& chl_des = entries[child].descendants;
  194|  4.26M|        for (auto anc_of_par : par_anc) {
  ------------------
  |  Branch (194:30): [True: 4.26M, False: 2.39M]
  ------------------
  195|  4.26M|            entries[anc_of_par].descendants |= chl_des;
  196|  4.26M|        }
  197|       |        // To each descendant of the child, add those ancestors.
  198|  4.57M|        for (auto dec_of_chl : Descendants(child)) {
  ------------------
  |  Branch (198:30): [True: 4.57M, False: 2.39M]
  ------------------
  199|  4.57M|            entries[dec_of_chl].ancestors |= par_anc;
  200|  4.57M|        }
  201|  2.39M|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE18RemoveTransactionsERKS3_:
  161|   782k|    {
  162|   782k|        m_used -= del;
  163|       |        // Remove now-unused trailing entries.
  164|   884k|        while (!entries.empty() && !m_used[entries.size() - 1]) {
  ------------------
  |  Branch (164:16): [True: 881k, False: 3.07k]
  |  Branch (164:36): [True: 102k, False: 779k]
  ------------------
  165|   102k|            entries.pop_back();
  166|   102k|        }
  167|       |        // Remove the deleted transactions from ancestors/descendants of other transactions. Note
  168|       |        // that the deleted positions will retain old feerate and dependency information. This does
  169|       |        // not matter as they will be overwritten by AddTransaction if they get used again.
  170|  48.9M|        for (auto& entry : entries) {
  ------------------
  |  Branch (170:26): [True: 48.9M, False: 782k]
  ------------------
  171|  48.9M|            entry.ancestors &= m_used;
  172|  48.9M|            entry.descendants &= m_used;
  173|  48.9M|        }
  174|   782k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE7FeeRateEj:
  123|  18.3M|    const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEEC2ERKS4_:
   72|  58.7k|    DepGraph(const DepGraph&) noexcept = default;
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEEaSEOS4_:
   75|  4.35k|    DepGraph& operator=(DepGraph&&) noexcept = default;
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE9PositionsEv:
  117|   153M|    const SetType& Positions() const noexcept { return m_used; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE22FindConnectedComponentERKS3_:
  292|  18.9M|    {
  293|  18.9M|        if (todo.None()) return todo;
  ------------------
  |  Branch (293:13): [True: 3.32k, False: 18.9M]
  ------------------
  294|  18.9M|        return GetConnectedComponent(todo, todo.First());
  295|  18.9M|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE7FeeRateEj:
  125|  51.4M|    FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE21GetConnectedComponentERKS3_j:
  267|  24.7M|    {
  268|  24.7M|        Assume(todo[tx]);
  ------------------
  |  |  128|  24.7M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  269|  24.7M|        Assume(todo.IsSubsetOf(m_used));
  ------------------
  |  |  128|  24.7M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  270|  24.7M|        auto to_add = SetType::Singleton(tx);
  271|  24.7M|        SetType ret;
  272|  55.6M|        do {
  273|  55.6M|            SetType old = ret;
  274|   111M|            for (auto add : to_add) {
  ------------------
  |  Branch (274:27): [True: 111M, False: 55.6M]
  ------------------
  275|   111M|                ret |= Descendants(add);
  276|   111M|                ret |= Ancestors(add);
  277|   111M|            }
  278|  55.6M|            ret &= todo;
  279|  55.6M|            to_add = ret - old;
  280|  55.6M|        } while (to_add.Any());
  ------------------
  |  Branch (280:18): [True: 30.8M, False: 24.7M]
  ------------------
  281|  24.7M|        return ret;
  282|  24.7M|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE11DescendantsEj:
  129|   135M|    const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE9AncestorsEj:
  127|   143M|    const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
_ZN17cluster_linearize18ChunkLinearizationIN13bitset_detail14MultiIntBitSetImLj2EEEEENSt3__16vectorI7FeeFracNS4_9allocatorIS6_EEEERKNS_8DepGraphIT_EENS4_4spanIKjLm18446744073709551615EEE:
  450|   379k|{
  451|   379k|    std::vector<FeeFrac> ret;
  452|  1.36M|    for (DepGraphIndex i : linearization) {
  ------------------
  |  Branch (452:26): [True: 1.36M, False: 379k]
  ------------------
  453|       |        /** The new chunk to be added, initially a singleton. */
  454|  1.36M|        auto new_chunk = depgraph.FeeRate(i);
  455|       |        // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
  456|  1.59M|        while (!ret.empty() && ByRatio{new_chunk} > ByRatio{ret.back()}) {
  ------------------
  |  Branch (456:16): [True: 1.19M, False: 394k]
  |  Branch (456:16): [True: 230k, False: 1.36M]
  |  Branch (456:32): [True: 230k, False: 966k]
  ------------------
  457|   230k|            new_chunk += ret.back();
  458|   230k|            ret.pop_back();
  459|   230k|        }
  460|       |        // Actually move that new chunk into the chunking.
  461|  1.36M|        ret.push_back(std::move(new_chunk));
  462|  1.36M|    }
  463|   379k|    return ret;
  464|   379k|}
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE11IsConnectedERKS3_:
  302|  7.95M|    {
  303|  7.95M|        return FindConnectedComponent(subset) == subset;
  304|  7.95M|    }
txgraph.cpp:_ZN17cluster_linearize9LinearizeIN13bitset_detail14MultiIntBitSetImLj2EEETkNS_16StrongComparatorIjEEZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEE3$_4EENS5_5tupleIJNS5_6vectorIjNS5_9allocatorIjEEEEbmEEERKNS_8DepGraphIT_EEmmRKT0_NS6_IKjLm18446744073709551615EEEb:
 1823|  2.67k|{
 1824|       |    /** Initialize a spanning forest data structure for this cluster. */
 1825|  2.67k|    SpanningForestState forest(depgraph, rng_seed);
 1826|  2.67k|    if (!old_linearization.empty()) {
  ------------------
  |  Branch (1826:9): [True: 2.57k, False: 95]
  ------------------
 1827|  2.57k|        forest.LoadLinearization(old_linearization);
 1828|  2.57k|        if (!is_topological) forest.MakeTopological();
  ------------------
  |  Branch (1828:13): [True: 0, False: 2.57k]
  ------------------
 1829|  2.57k|    } else {
 1830|     95|        forest.MakeTopological();
 1831|     95|    }
 1832|       |    // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
 1833|       |    // is found.
 1834|  2.67k|    if (forest.GetCost() < max_cost) {
  ------------------
  |  Branch (1834:9): [True: 2.67k, False: 0]
  ------------------
 1835|  2.67k|        forest.StartOptimizing();
 1836|  75.8k|        do {
 1837|  75.8k|            if (!forest.OptimizeStep()) break;
  ------------------
  |  Branch (1837:17): [True: 2.67k, False: 73.1k]
  ------------------
 1838|  75.8k|        } while (forest.GetCost() < max_cost);
  ------------------
  |  Branch (1838:18): [True: 73.1k, False: 0]
  ------------------
 1839|  2.67k|    }
 1840|       |    // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
 1841|       |    // minimal.
 1842|  2.67k|    bool optimal = false;
 1843|  2.67k|    if (forest.GetCost() < max_cost) {
  ------------------
  |  Branch (1843:9): [True: 2.67k, False: 0]
  ------------------
 1844|  2.67k|        forest.StartMinimizing();
 1845|   128k|        do {
 1846|   128k|            if (!forest.MinimizeStep()) {
  ------------------
  |  Branch (1846:17): [True: 2.67k, False: 125k]
  ------------------
 1847|  2.67k|                optimal = true;
 1848|  2.67k|                break;
 1849|  2.67k|            }
 1850|   128k|        } while (forest.GetCost() < max_cost);
  ------------------
  |  Branch (1850:18): [True: 125k, False: 0]
  ------------------
 1851|  2.67k|    }
 1852|  2.67k|    return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()};
 1853|  2.67k|}
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEEC2ERKNS_8DepGraphIS3_EEmRKS4_:
 1183|  2.67k|        m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost)
 1184|  2.67k|    {
 1185|  2.67k|        m_cost.InitializeBegin();
 1186|  2.67k|        m_transaction_idxs = depgraph.Positions();
 1187|  2.67k|        auto num_transactions = m_transaction_idxs.Count();
 1188|  2.67k|        m_tx_data.resize(depgraph.PositionRange());
 1189|  2.67k|        m_set_info.resize(num_transactions);
 1190|  2.67k|        m_reachable.resize(num_transactions);
 1191|  2.67k|        m_suboptimal_chunks.reserve(num_transactions);
 1192|  2.67k|        size_t num_chunks = 0;
 1193|  2.67k|        size_t num_deps = 0;
 1194|   112k|        for (auto tx_idx : m_transaction_idxs) {
  ------------------
  |  Branch (1194:26): [True: 112k, False: 2.67k]
  ------------------
 1195|       |            // Fill in transaction data.
 1196|   112k|            auto& tx_data = m_tx_data[tx_idx];
 1197|   112k|            tx_data.parents = depgraph.GetReducedParents(tx_idx);
 1198|   112k|            for (auto parent_idx : tx_data.parents) {
  ------------------
  |  Branch (1198:34): [True: 51.7k, False: 112k]
  ------------------
 1199|  51.7k|                m_tx_data[parent_idx].children.Set(tx_idx);
 1200|  51.7k|            }
 1201|   112k|            num_deps += tx_data.parents.Count();
 1202|       |            // Create a singleton chunk for it.
 1203|   112k|            tx_data.chunk_idx = num_chunks;
 1204|   112k|            m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx);
 1205|   112k|        }
 1206|       |        // Set the reachable transactions for each chunk to the transactions' parents and children.
 1207|   115k|        for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) {
  ------------------
  |  Branch (1207:36): [True: 112k, False: 2.67k]
  ------------------
 1208|   112k|            auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()];
 1209|   112k|            m_reachable[chunk_idx].first = tx_data.parents;
 1210|   112k|            m_reachable[chunk_idx].second = tx_data.children;
 1211|   112k|        }
 1212|  2.67k|        Assume(num_chunks == num_transactions);
  ------------------
  |  |  128|  2.67k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1213|       |        // Mark all chunk sets as chunks.
 1214|  2.67k|        m_chunk_idxs = SetType::Fill(num_chunks);
 1215|  2.67k|        m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps);
 1216|  2.67k|    }
_ZN17cluster_linearize7SetInfoIN13bitset_detail14MultiIntBitSetImLj2EEEEC2Ev:
  370|   112k|    SetInfo() noexcept = default;
_ZN17cluster_linearize7SetInfoIN13bitset_detail14MultiIntBitSetImLj2EEEEC2ERKNS_8DepGraphIS3_EEj:
  377|  13.8M|        transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE17LoadLinearizationENSt3__14spanIKjLm18446744073709551615EEE:
 1222|  2.57k|    {
 1223|       |        // Add transactions one by one, in order of existing linearization.
 1224|   112k|        for (DepGraphIndex tx_idx : old_linearization) {
  ------------------
  |  Branch (1224:35): [True: 112k, False: 2.57k]
  ------------------
 1225|   112k|            auto chunk_idx = m_tx_data[tx_idx].chunk_idx;
 1226|       |            // Merge the chunk upwards, as long as merging succeeds.
 1227|   150k|            while (true) {
  ------------------
  |  Branch (1227:20): [True: 150k, Folded]
  ------------------
 1228|   150k|                chunk_idx = MergeStep<false>(chunk_idx);
 1229|   150k|                if (chunk_idx == INVALID_SET_IDX) break;
  ------------------
  |  Branch (1229:21): [True: 112k, False: 37.8k]
  ------------------
 1230|   150k|            }
 1231|   112k|        }
 1232|  2.57k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE9MergeStepILb0EEEhh:
 1060|   150k|    {
 1061|   150k|        auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx);
 1062|   150k|        if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX;
  ------------------
  |  Branch (1062:13): [True: 112k, False: 37.8k]
  ------------------
 1063|  37.8k|        chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx);
 1064|  37.8k|        Assume(chunk_idx != INVALID_SET_IDX);
  ------------------
  |  |  128|  37.8k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1065|  37.8k|        return chunk_idx;
 1066|   150k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE18PickMergeCandidateILb0EEEhh:
 1010|   150k|    {
 1011|   150k|        m_cost.PickMergeCandidateBegin();
 1012|       |        /** Information about the chunk. */
 1013|   150k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|   150k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1014|   150k|        auto& chunk_info = m_set_info[chunk_idx];
 1015|       |        // Iterate over all chunks reachable from this one. For those depended-on chunks,
 1016|       |        // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
 1017|       |        // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
 1018|       |        // among them.
 1019|       |
 1020|       |        /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when
 1021|       |         *  looking for candidate chunks to merge with. Initially, this is the original chunk's
 1022|       |         *  feerate, but is updated to be the current best candidate whenever one is found. */
 1023|   150k|        FeeFrac best_other_chunk_feerate = chunk_info.feerate;
 1024|       |        /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */
 1025|   150k|        SetIdx best_other_chunk_idx = INVALID_SET_IDX;
 1026|       |        /** We generate random tiebreak values to pick between equal-feerate candidate chunks.
 1027|       |         *  This variable stores the tiebreak of the current best candidate. */
 1028|   150k|        uint64_t best_other_chunk_tiebreak{0};
 1029|       |
 1030|       |        /** Which parent/child transactions we still need to process the chunks for. */
 1031|   150k|        auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first;
  ------------------
  |  Branch (1031:21): [Folded, False: 150k]
  ------------------
 1032|   150k|        unsigned steps = 0;
 1033|   235k|        while (todo.Any()) {
  ------------------
  |  Branch (1033:16): [True: 84.2k, False: 150k]
  ------------------
 1034|  84.2k|            ++steps;
 1035|       |            // Find a chunk for a transaction in todo, and remove all its transactions from todo.
 1036|  84.2k|            auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx;
 1037|  84.2k|            auto& reached_chunk_info = m_set_info[reached_chunk_idx];
 1038|  84.2k|            todo -= reached_chunk_info.transactions;
 1039|       |            // See if it has an acceptable feerate.
 1040|  84.2k|            auto cmp = DownWard ? ByRatio{best_other_chunk_feerate} <=> ByRatio{reached_chunk_info.feerate}
  ------------------
  |  Branch (1040:24): [Folded, False: 84.2k]
  ------------------
 1041|  84.2k|                                : ByRatio{reached_chunk_info.feerate} <=> ByRatio{best_other_chunk_feerate};
 1042|  84.2k|            if (cmp > 0) continue;
  ------------------
  |  Branch (1042:17): [True: 33.6k, False: 50.6k]
  ------------------
 1043|  50.6k|            uint64_t tiebreak = m_rng.rand64();
 1044|  50.6k|            if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
  ------------------
  |  Branch (1044:17): [True: 11.7k, False: 38.9k]
  |  Branch (1044:28): [True: 31.5k, False: 7.43k]
  ------------------
 1045|  43.2k|                best_other_chunk_feerate = reached_chunk_info.feerate;
 1046|  43.2k|                best_other_chunk_idx = reached_chunk_idx;
 1047|  43.2k|                best_other_chunk_tiebreak = tiebreak;
 1048|  43.2k|            }
 1049|  50.6k|        }
 1050|   150k|        Assume(steps <= m_set_info.size());
  ------------------
  |  |  128|   150k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1051|       |
 1052|   150k|        m_cost.PickMergeCandidateEnd(/*num_steps=*/steps);
 1053|   150k|        return best_other_chunk_idx;
 1054|   150k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE19MergeChunksDirectedILb0EEEhhh:
  999|  37.8k|    {
 1000|       |        if constexpr (DownWard) {
 1001|       |            return MergeChunks(chunk_idx, merge_chunk_idx);
 1002|  37.8k|        } else {
 1003|  37.8k|            return MergeChunks(merge_chunk_idx, chunk_idx);
 1004|  37.8k|        }
 1005|  37.8k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE11MergeChunksEhh:
  951|  39.5k|    {
  952|  39.5k|        m_cost.MergeChunksBegin();
  953|  39.5k|        Assume(m_chunk_idxs[top_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  954|  39.5k|        Assume(m_chunk_idxs[bottom_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  955|  39.5k|        auto& top_chunk_info = m_set_info[top_idx];
  956|  39.5k|        auto& bottom_chunk_info = m_set_info[bottom_idx];
  957|       |        // Count the number of dependencies between bottom_chunk and top_chunk, remembering the
  958|       |        // per-transaction counts so the picking loop below does not need to recompute the
  959|       |        // intersections.
  960|  39.5k|        unsigned num_deps{0};
  961|  39.5k|        std::array<SetIdx, SetType::Size()> counts;
  962|   140k|        for (auto tx_idx : top_chunk_info.transactions) {
  ------------------
  |  Branch (962:26): [True: 140k, False: 39.5k]
  ------------------
  963|   140k|            auto& tx_data = m_tx_data[tx_idx];
  964|   140k|            auto count = (tx_data.children & bottom_chunk_info.transactions).Count();
  965|   140k|            counts[tx_idx] = count;
  966|   140k|            num_deps += count;
  967|   140k|        }
  968|  39.5k|        m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count());
  969|  39.5k|        Assume(num_deps > 0);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  970|       |        // Uniformly randomly pick one of them and activate it.
  971|  39.5k|        unsigned pick = m_rng.randrange(num_deps);
  972|  39.5k|        unsigned num_steps = 0;
  973|  83.7k|        for (auto tx_idx : top_chunk_info.transactions) {
  ------------------
  |  Branch (973:26): [True: 83.7k, False: 0]
  ------------------
  974|  83.7k|            ++num_steps;
  975|  83.7k|            auto count = counts[tx_idx];
  976|  83.7k|            if (pick < count) {
  ------------------
  |  Branch (976:17): [True: 39.5k, False: 44.2k]
  ------------------
  977|  39.5k|                auto& tx_data = m_tx_data[tx_idx];
  978|  39.5k|                auto intersect = tx_data.children & bottom_chunk_info.transactions;
  979|  39.6k|                for (auto child_idx : intersect) {
  ------------------
  |  Branch (979:37): [True: 39.6k, False: 0]
  ------------------
  980|  39.6k|                    if (pick == 0) {
  ------------------
  |  Branch (980:25): [True: 39.5k, False: 95]
  ------------------
  981|  39.5k|                        m_cost.MergeChunksEnd(/*num_steps=*/num_steps);
  982|  39.5k|                        return Activate(tx_idx, child_idx);
  983|  39.5k|                    }
  984|     95|                    --pick;
  985|     95|                }
  986|      0|                Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  987|      0|                break;
  988|  39.5k|            }
  989|  44.2k|            pick -= count;
  990|  44.2k|        }
  991|      0|        Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  992|      0|        return INVALID_SET_IDX;
  993|  39.5k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE8ActivateEjj:
  819|  39.5k|    {
  820|  39.5k|        m_cost.ActivateBegin();
  821|       |        // Gather and check information about the parent and child transactions.
  822|  39.5k|        auto& parent_data = m_tx_data[parent_idx];
  823|  39.5k|        auto& child_data = m_tx_data[child_idx];
  824|  39.5k|        Assume(parent_data.children[child_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  825|  39.5k|        Assume(!parent_data.active_children[child_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  826|       |        // Get the set index of the chunks the parent and child are currently in. The parent chunk
  827|       |        // will become the top set of the newly activated dependency, while the child chunk will be
  828|       |        // grown to become the merged chunk.
  829|  39.5k|        auto parent_chunk_idx = parent_data.chunk_idx;
  830|  39.5k|        auto child_chunk_idx = child_data.chunk_idx;
  831|  39.5k|        Assume(parent_chunk_idx != child_chunk_idx);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  832|  39.5k|        Assume(m_chunk_idxs[parent_chunk_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  833|  39.5k|        Assume(m_chunk_idxs[child_chunk_idx]);
  ------------------
  |  |  128|  39.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  834|  39.5k|        auto& top_info = m_set_info[parent_chunk_idx];
  835|  39.5k|        auto& bottom_info = m_set_info[child_chunk_idx];
  836|       |
  837|       |        // Consider the following example:
  838|       |        //
  839|       |        //    A           A     There are two chunks, ABC and DEF, and the inactive E->C dependency
  840|       |        //   / \         / \    is activated, resulting in a single chunk ABCDEF.
  841|       |        //  B   C       B   C
  842|       |        //      :  ==>      |   Dependency | top set before | top set after | change
  843|       |        //  D   E       D   E   B->A       | AC             | ACDEF         | +DEF
  844|       |        //   \ /         \ /    C->A       | AB             | AB            |
  845|       |        //    F           F     F->D       | D              | D             |
  846|       |        //                      F->E       | E              | ABCE          | +ABC
  847|       |        //
  848|       |        // The common pattern here is that any dependency which has the parent or child of the
  849|       |        // dependency being activated (E->C here) in its top set, will have the opposite part added
  850|       |        // to it. This is true for B->A and F->E, but not for C->A and F->D.
  851|       |        //
  852|       |        // Traverse the old parent chunk top_info (ABC in example), and add bottom_info (DEF) to
  853|       |        // every dependency's top set which has the parent (C) in it. At the same time, change the
  854|       |        // chunk_idx for each to be child_chunk_idx, which becomes the set for the merged chunk.
  855|   140k|        for (auto tx_idx : top_info.transactions) {
  ------------------
  |  Branch (855:26): [True: 140k, False: 39.5k]
  ------------------
  856|   140k|            auto& tx_data = m_tx_data[tx_idx];
  857|   140k|            tx_data.chunk_idx = child_chunk_idx;
  858|   140k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (858:37): [True: 101k, False: 140k]
  ------------------
  859|   101k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  860|   101k|                if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info;
  ------------------
  |  Branch (860:21): [True: 46.5k, False: 54.8k]
  ------------------
  861|   101k|            }
  862|   140k|        }
  863|       |        // Traverse the old child chunk bottom_info (DEF in example), and add top_info (ABC) to
  864|       |        // every dependency's top set which has the child (E) in it.
  865|  80.6k|        for (auto tx_idx : bottom_info.transactions) {
  ------------------
  |  Branch (865:26): [True: 80.6k, False: 39.5k]
  ------------------
  866|  80.6k|            auto& tx_data = m_tx_data[tx_idx];
  867|  80.6k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (867:37): [True: 41.1k, False: 80.6k]
  ------------------
  868|  41.1k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  869|  41.1k|                if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info;
  ------------------
  |  Branch (869:21): [True: 10.8k, False: 30.3k]
  ------------------
  870|  41.1k|            }
  871|  80.6k|        }
  872|       |        // Merge top_info into bottom_info, which becomes the merged chunk.
  873|  39.5k|        bottom_info |= top_info;
  874|       |        // Compute merged sets of reachable transactions from the new chunk, based on the input
  875|       |        // chunks' reachable sets.
  876|  39.5k|        m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first;
  877|  39.5k|        m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second;
  878|  39.5k|        m_reachable[child_chunk_idx].first -= bottom_info.transactions;
  879|  39.5k|        m_reachable[child_chunk_idx].second -= bottom_info.transactions;
  880|       |        // Make parent chunk the set for the new active dependency.
  881|  39.5k|        parent_data.dep_top_idx[child_idx] = parent_chunk_idx;
  882|  39.5k|        parent_data.active_children.Set(child_idx);
  883|  39.5k|        m_chunk_idxs.Reset(parent_chunk_idx);
  884|       |        // Return the newly merged chunk.
  885|  39.5k|        m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1);
  886|  39.5k|        return child_chunk_idx;
  887|  39.5k|    }
_ZN17cluster_linearize7SetInfoIN13bitset_detail14MultiIntBitSetImLj2EEEEoRERKS4_:
  393|  5.82M|    {
  394|  5.82M|        Assume(!transactions.Overlaps(other.transactions));
  ------------------
  |  |  128|  5.82M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  395|  5.82M|        transactions |= other.transactions;
  396|  5.82M|        feerate += other.feerate;
  397|  5.82M|        return *this;
  398|  5.82M|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE15MakeTopologicalEv:
 1236|     95|    {
 1237|     95|        m_cost.MakeTopologicalBegin();
 1238|     95|        Assume(m_suboptimal_chunks.empty());
  ------------------
  |  |  128|     95|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1239|       |        /** What direction to initially merge chunks in; one of the two directions is enough. This
 1240|       |         *  is sufficient because if a non-topological inactive dependency exists between two
 1241|       |         *  chunks, at least one of the two chunks will eventually be processed in a direction that
 1242|       |         *  discovers it - either the lower chunk tries upward, or the upper chunk tries downward.
 1243|       |         *  Chunks that are the result of the merging are always tried in both directions. */
 1244|     95|        unsigned init_dir = m_rng.randbool();
 1245|       |        /** Which chunks are the result of merging, and thus need merge attempts in both
 1246|       |         *  directions. */
 1247|     95|        SetType merged_chunks;
 1248|       |        // Mark chunks as suboptimal.
 1249|     95|        m_suboptimal_idxs = m_chunk_idxs;
 1250|     95|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1250:29): [True: 0, False: 95]
  ------------------
 1251|      0|            m_suboptimal_chunks.emplace_back(chunk_idx);
 1252|       |            // Randomize the initial order of suboptimal chunks in the queue.
 1253|      0|            SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());
 1254|      0|            if (j != m_suboptimal_chunks.size() - 1) {
  ------------------
  |  Branch (1254:17): [True: 0, False: 0]
  ------------------
 1255|      0|                std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);
 1256|      0|            }
 1257|      0|        }
 1258|     95|        unsigned chunks = m_chunk_idxs.Count();
 1259|     95|        unsigned steps = 0;
 1260|     95|        while (!m_suboptimal_chunks.empty()) {
  ------------------
  |  Branch (1260:16): [True: 0, False: 95]
  ------------------
 1261|      0|            ++steps;
 1262|       |            // Pop an entry from the potentially-suboptimal chunk queue.
 1263|      0|            SetIdx chunk_idx = m_suboptimal_chunks.front();
 1264|      0|            m_suboptimal_chunks.pop_front();
 1265|      0|            Assume(m_suboptimal_idxs[chunk_idx]);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1266|      0|            m_suboptimal_idxs.Reset(chunk_idx);
 1267|       |            // If what was popped is not currently a chunk, continue. This may
 1268|       |            // happen when it was merged with something else since being added.
 1269|      0|            if (!m_chunk_idxs[chunk_idx]) continue;
  ------------------
  |  Branch (1269:17): [True: 0, False: 0]
  ------------------
 1270|       |            /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */
 1271|      0|            unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1;
  ------------------
  |  Branch (1271:34): [True: 0, False: 0]
  ------------------
 1272|      0|            int flip = m_rng.randbool();
 1273|      0|            for (int i = 0; i < 2; ++i) {
  ------------------
  |  Branch (1273:29): [True: 0, False: 0]
  ------------------
 1274|      0|                if (i ^ flip) {
  ------------------
  |  Branch (1274:21): [True: 0, False: 0]
  ------------------
 1275|      0|                    if (!(direction & 1)) continue;
  ------------------
  |  Branch (1275:25): [True: 0, False: 0]
  ------------------
 1276|       |                    // Attempt to merge the chunk upwards.
 1277|      0|                    auto result_up = MergeStep<false>(chunk_idx);
 1278|      0|                    if (result_up != INVALID_SET_IDX) {
  ------------------
  |  Branch (1278:25): [True: 0, False: 0]
  ------------------
 1279|      0|                        if (!m_suboptimal_idxs[result_up]) {
  ------------------
  |  Branch (1279:29): [True: 0, False: 0]
  ------------------
 1280|      0|                            m_suboptimal_idxs.Set(result_up);
 1281|      0|                            m_suboptimal_chunks.push_back(result_up);
 1282|      0|                        }
 1283|      0|                        merged_chunks.Set(result_up);
 1284|      0|                        break;
 1285|      0|                    }
 1286|      0|                } else {
 1287|      0|                    if (!(direction & 2)) continue;
  ------------------
  |  Branch (1287:25): [True: 0, False: 0]
  ------------------
 1288|       |                    // Attempt to merge the chunk downwards.
 1289|      0|                    auto result_down = MergeStep<true>(chunk_idx);
 1290|      0|                    if (result_down != INVALID_SET_IDX) {
  ------------------
  |  Branch (1290:25): [True: 0, False: 0]
  ------------------
 1291|      0|                        if (!m_suboptimal_idxs[result_down]) {
  ------------------
  |  Branch (1291:29): [True: 0, False: 0]
  ------------------
 1292|      0|                            m_suboptimal_idxs.Set(result_down);
 1293|      0|                            m_suboptimal_chunks.push_back(result_down);
 1294|      0|                        }
 1295|      0|                        merged_chunks.Set(result_down);
 1296|      0|                        break;
 1297|      0|                    }
 1298|      0|                }
 1299|      0|            }
 1300|      0|        }
 1301|     95|        m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps);
 1302|     95|    }
_ZNK17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE7GetCostEv:
 1643|   207k|    uint64_t GetCost() const noexcept { return m_cost.GetCost(); }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE15StartOptimizingEv:
 1306|  2.67k|    {
 1307|  2.67k|        m_cost.StartOptimizingBegin();
 1308|  2.67k|        Assume(m_suboptimal_chunks.empty());
  ------------------
  |  |  128|  2.67k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1309|       |        // Mark chunks suboptimal.
 1310|  2.67k|        m_suboptimal_idxs = m_chunk_idxs;
 1311|  75.0k|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1311:29): [True: 75.0k, False: 2.67k]
  ------------------
 1312|  75.0k|            m_suboptimal_chunks.push_back(chunk_idx);
 1313|       |            // Randomize the initial order of suboptimal chunks in the queue.
 1314|  75.0k|            SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());
 1315|  75.0k|            if (j != m_suboptimal_chunks.size() - 1) {
  ------------------
  |  Branch (1315:17): [True: 66.6k, False: 8.37k]
  ------------------
 1316|  66.6k|                std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);
 1317|  66.6k|            }
 1318|  75.0k|        }
 1319|  2.67k|        m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size());
 1320|  2.67k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE12OptimizeStepEv:
 1324|  75.8k|    {
 1325|  75.8k|        auto chunk_idx = PickChunkToOptimize();
 1326|  75.8k|        if (chunk_idx == INVALID_SET_IDX) {
  ------------------
  |  Branch (1326:13): [True: 95, False: 75.7k]
  ------------------
 1327|       |            // No improvable chunk was found, we are done.
 1328|     95|            return false;
 1329|     95|        }
 1330|  75.7k|        auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx);
 1331|  75.7k|        if (parent_idx == TxIdx(-1)) {
  ------------------
  |  Branch (1331:13): [True: 75.0k, False: 697]
  ------------------
 1332|       |            // Nothing to improve in chunk_idx. Need to continue with other chunks, if any.
 1333|  75.0k|            return !m_suboptimal_chunks.empty();
 1334|  75.0k|        }
 1335|       |        // Deactivate the found dependency and then make the state topological again with a
 1336|       |        // sequence of merges.
 1337|    697|        Improve(parent_idx, child_idx);
 1338|    697|        return true;
 1339|  75.7k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE19PickChunkToOptimizeEv:
 1123|  75.8k|    {
 1124|  75.8k|        m_cost.PickChunkToOptimizeBegin();
 1125|  75.8k|        unsigned steps{0};
 1126|  75.8k|        while (!m_suboptimal_chunks.empty()) {
  ------------------
  |  Branch (1126:16): [True: 75.7k, False: 95]
  ------------------
 1127|  75.7k|            ++steps;
 1128|       |            // Pop an entry from the potentially-suboptimal chunk queue.
 1129|  75.7k|            SetIdx chunk_idx = m_suboptimal_chunks.front();
 1130|  75.7k|            Assume(m_suboptimal_idxs[chunk_idx]);
  ------------------
  |  |  128|  75.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1131|  75.7k|            m_suboptimal_idxs.Reset(chunk_idx);
 1132|  75.7k|            m_suboptimal_chunks.pop_front();
 1133|  75.7k|            if (m_chunk_idxs[chunk_idx]) {
  ------------------
  |  Branch (1133:17): [True: 75.7k, False: 0]
  ------------------
 1134|  75.7k|                m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
 1135|  75.7k|                return chunk_idx;
 1136|  75.7k|            }
 1137|       |            // If what was popped is not currently a chunk, continue. This may
 1138|       |            // happen when a split chunk merges in Improve() with one or more existing chunks that
 1139|       |            // are themselves on the suboptimal queue already.
 1140|  75.7k|        }
 1141|     95|        m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
 1142|     95|        return INVALID_SET_IDX;
 1143|  75.8k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE21PickDependencyToSplitEh:
 1147|  75.7k|    {
 1148|  75.7k|        m_cost.PickDependencyToSplitBegin();
 1149|  75.7k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|  75.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1150|  75.7k|        auto& chunk_info = m_set_info[chunk_idx];
 1151|       |
 1152|       |        // Remember the best dependency {par, chl} seen so far.
 1153|  75.7k|        std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)};
 1154|  75.7k|        uint64_t candidate_tiebreak = 0;
 1155|       |        // Iterate over all transactions.
 1156|   122k|        for (auto tx_idx : chunk_info.transactions) {
  ------------------
  |  Branch (1156:26): [True: 122k, False: 75.7k]
  ------------------
 1157|   122k|            const auto& tx_data = m_tx_data[tx_idx];
 1158|       |            // Iterate over all active child dependencies of the transaction.
 1159|   122k|            for (auto child_idx : tx_data.active_children) {
  ------------------
  |  Branch (1159:33): [True: 47.2k, False: 122k]
  ------------------
 1160|  47.2k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
 1161|       |                // Skip if this dependency is ineligible (the top chunk that would be created
 1162|       |                // does not have higher feerate than the chunk it is currently part of).
 1163|  47.2k|                auto cmp = ByRatio{dep_top_info.feerate} <=> ByRatio{chunk_info.feerate};
 1164|  47.2k|                if (cmp <= 0) continue;
  ------------------
  |  Branch (1164:21): [True: 45.4k, False: 1.73k]
  ------------------
 1165|       |                // Generate a random tiebreak for this dependency, and reject it if its tiebreak
 1166|       |                // is worse than the best so far. This means that among all eligible
 1167|       |                // dependencies, a uniformly random one will be chosen.
 1168|  1.73k|                uint64_t tiebreak = m_rng.rand64();
 1169|  1.73k|                if (tiebreak < candidate_tiebreak) continue;
  ------------------
  |  Branch (1169:21): [True: 696, False: 1.03k]
  ------------------
 1170|       |                // Remember this as our (new) candidate dependency.
 1171|  1.03k|                candidate_dep = {tx_idx, child_idx};
 1172|  1.03k|                candidate_tiebreak = tiebreak;
 1173|  1.03k|            }
 1174|   122k|        }
 1175|  75.7k|        m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count());
 1176|  75.7k|        return candidate_dep;
 1177|  75.7k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE7ImproveEjj:
 1088|    697|    {
 1089|       |        // Deactivate the specified dependency, splitting it into two new chunks: a top containing
 1090|       |        // the parent, and a bottom containing the child. The top should have a higher feerate.
 1091|    697|        auto [parent_chunk_idx, child_chunk_idx] = Deactivate(parent_idx, child_idx);
 1092|       |
 1093|       |        // At this point we have exactly two chunks which may violate topology constraints (the
 1094|       |        // parent chunk and child chunk that were produced by deactivation). We can fix
 1095|       |        // these using just merge sequences, one upwards and one downwards, avoiding the need for a
 1096|       |        // full MakeTopological.
 1097|    697|        const auto& parent_reachable = m_reachable[parent_chunk_idx].first;
 1098|    697|        const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
 1099|    697|        if (parent_reachable.Overlaps(child_chunk_txn)) {
  ------------------
  |  Branch (1099:13): [True: 697, False: 0]
  ------------------
 1100|       |            // The parent chunk has a dependency on a transaction in the child chunk. In this case,
 1101|       |            // the parent needs to merge back with the child chunk (a self-merge), and no other
 1102|       |            // merges are needed. Special-case this, so the overhead of PickMergeCandidate and
 1103|       |            // MergeSequence can be avoided.
 1104|       |
 1105|       |            // In the self-merge, the roles reverse: the parent chunk (from the split) depends
 1106|       |            // on the child chunk, so child_chunk_idx is the "top" and parent_chunk_idx is the
 1107|       |            // "bottom" for MergeChunks.
 1108|    697|            auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
 1109|    697|            if (!m_suboptimal_idxs[merged_chunk_idx]) {
  ------------------
  |  Branch (1109:17): [True: 697, False: 0]
  ------------------
 1110|    697|                m_suboptimal_idxs.Set(merged_chunk_idx);
 1111|    697|                m_suboptimal_chunks.push_back(merged_chunk_idx);
 1112|    697|            }
 1113|    697|        } else {
 1114|       |            // Merge the top chunk with lower-feerate chunks it depends on.
 1115|      0|            MergeSequence<false>(parent_chunk_idx);
 1116|       |            // Merge the bottom chunk with higher-feerate chunks that depend on it.
 1117|      0|            MergeSequence<true>(child_chunk_idx);
 1118|      0|        }
 1119|    697|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE10DeactivateEjj:
  892|  23.3k|    {
  893|  23.3k|        m_cost.DeactivateBegin();
  894|       |        // Gather and check information about the parent transactions.
  895|  23.3k|        auto& parent_data = m_tx_data[parent_idx];
  896|  23.3k|        Assume(parent_data.children[child_idx]);
  ------------------
  |  |  128|  23.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  897|  23.3k|        Assume(parent_data.active_children[child_idx]);
  ------------------
  |  |  128|  23.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  898|       |        // Get the top set of the active dependency (which will become the parent chunk) and the
  899|       |        // chunk set the transactions are currently in (which will become the bottom chunk).
  900|  23.3k|        auto parent_chunk_idx = parent_data.dep_top_idx[child_idx];
  901|  23.3k|        auto child_chunk_idx = parent_data.chunk_idx;
  902|  23.3k|        Assume(parent_chunk_idx != child_chunk_idx);
  ------------------
  |  |  128|  23.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  903|  23.3k|        Assume(m_chunk_idxs[child_chunk_idx]);
  ------------------
  |  |  128|  23.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  904|  23.3k|        Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk
  ------------------
  |  |  128|  23.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  905|  23.3k|        auto& top_info = m_set_info[parent_chunk_idx];
  906|  23.3k|        auto& bottom_info = m_set_info[child_chunk_idx];
  907|       |
  908|       |        // Remove the active dependency.
  909|  23.3k|        parent_data.active_children.Reset(child_idx);
  910|  23.3k|        m_chunk_idxs.Set(parent_chunk_idx);
  911|  23.3k|        auto ntx = bottom_info.transactions.Count();
  912|       |        // Subtract the top_info from the bottom_info, as it will become the child chunk.
  913|  23.3k|        bottom_info -= top_info;
  914|       |        // See the comment above in Activate(). We perform the opposite operations here, removing
  915|       |        // instead of adding. Simultaneously, aggregate the top/bottom's union of parents/children.
  916|  23.3k|        SetType top_parents, top_children;
  917|  66.6k|        for (auto tx_idx : top_info.transactions) {
  ------------------
  |  Branch (917:26): [True: 66.6k, False: 23.3k]
  ------------------
  918|  66.6k|            auto& tx_data = m_tx_data[tx_idx];
  919|  66.6k|            tx_data.chunk_idx = parent_chunk_idx;
  920|  66.6k|            top_parents |= tx_data.parents;
  921|  66.6k|            top_children |= tx_data.children;
  922|  66.6k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (922:37): [True: 43.2k, False: 66.6k]
  ------------------
  923|  43.2k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  924|  43.2k|                if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info;
  ------------------
  |  Branch (924:21): [True: 21.5k, False: 21.6k]
  ------------------
  925|  43.2k|            }
  926|  66.6k|        }
  927|  23.3k|        SetType bottom_parents, bottom_children;
  928|  54.2k|        for (auto tx_idx : bottom_info.transactions) {
  ------------------
  |  Branch (928:26): [True: 54.2k, False: 23.3k]
  ------------------
  929|  54.2k|            auto& tx_data = m_tx_data[tx_idx];
  930|  54.2k|            bottom_parents |= tx_data.parents;
  931|  54.2k|            bottom_children |= tx_data.children;
  932|  54.2k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (932:37): [True: 30.8k, False: 54.2k]
  ------------------
  933|  30.8k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  934|  30.8k|                if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info;
  ------------------
  |  Branch (934:21): [True: 17.5k, False: 13.3k]
  ------------------
  935|  30.8k|            }
  936|  54.2k|        }
  937|       |        // Compute the new sets of reachable transactions for each new chunk, based on the
  938|       |        // top/bottom parents and children computed above.
  939|  23.3k|        m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions;
  940|  23.3k|        m_reachable[parent_chunk_idx].second = top_children - top_info.transactions;
  941|  23.3k|        m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions;
  942|  23.3k|        m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions;
  943|       |        // Return the two new set idxs.
  944|  23.3k|        m_cost.DeactivateEnd(/*num_deps=*/ntx - 1);
  945|  23.3k|        return {parent_chunk_idx, child_chunk_idx};
  946|  23.3k|    }
_ZN17cluster_linearize7SetInfoIN13bitset_detail14MultiIntBitSetImLj2EEEEmIERKS4_:
  402|  62.4k|    {
  403|  62.4k|        Assume(other.transactions.IsSubsetOf(transactions));
  ------------------
  |  |  128|  62.4k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  404|  62.4k|        transactions -= other.transactions;
  405|  62.4k|        feerate -= other.feerate;
  406|  62.4k|        return *this;
  407|  62.4k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE15StartMinimizingEv:
 1344|  2.67k|    {
 1345|  2.67k|        m_cost.StartMinimizingBegin();
 1346|  2.67k|        m_nonminimal_chunks.clear();
 1347|  2.67k|        m_nonminimal_chunks.reserve(m_transaction_idxs.Count());
 1348|       |        // Gather all chunks, and for each, add it with a random pivot in it, and a random initial
 1349|       |        // direction, to m_nonminimal_chunks.
 1350|  75.0k|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1350:29): [True: 75.0k, False: 2.67k]
  ------------------
 1351|  75.0k|            TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions);
 1352|  75.0k|            m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>());
 1353|       |            // Randomize the initial order of nonminimal chunks in the queue.
 1354|  75.0k|            SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size());
 1355|  75.0k|            if (j != m_nonminimal_chunks.size() - 1) {
  ------------------
  |  Branch (1355:17): [True: 66.5k, False: 8.44k]
  ------------------
 1356|  66.5k|                std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]);
 1357|  66.5k|            }
 1358|  75.0k|        }
 1359|  2.67k|        m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size());
 1360|  2.67k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE12PickRandomTxERKS3_:
  791|  96.7k|    {
  792|  96.7k|        Assume(tx_idxs.Any());
  ------------------
  |  |  128|  96.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  793|  96.7k|        unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
  794|   122k|        for (auto tx_idx : tx_idxs) {
  ------------------
  |  Branch (794:26): [True: 122k, False: 0]
  ------------------
  795|   122k|            if (pos == 0) return tx_idx;
  ------------------
  |  Branch (795:17): [True: 96.7k, False: 25.9k]
  ------------------
  796|  25.9k|            --pos;
  797|  25.9k|        }
  798|      0|        Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  799|      0|        return TxIdx(-1);
  800|  96.7k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE12MinimizeStepEv:
 1364|   128k|    {
 1365|       |        // If the queue of potentially-non-minimal chunks is empty, we are done.
 1366|   128k|        if (m_nonminimal_chunks.empty()) return false;
  ------------------
  |  Branch (1366:13): [True: 2.67k, False: 125k]
  ------------------
 1367|   125k|        m_cost.MinimizeStepBegin();
 1368|       |        // Pop an entry from the potentially-non-minimal chunk queue.
 1369|   125k|        auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front();
 1370|   125k|        m_nonminimal_chunks.pop_front();
 1371|   125k|        auto& chunk_info = m_set_info[chunk_idx];
 1372|       |        /** Whether to move the pivot down rather than up. */
 1373|   125k|        bool move_pivot_down = flags & 1;
 1374|       |        /** Whether this is already the second stage. */
 1375|   125k|        bool second_stage = flags & 2;
 1376|       |
 1377|       |        // Find a random dependency whose top and bottom set feerates are equal, and which has
 1378|       |        // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
 1379|   125k|        std::pair<TxIdx, TxIdx> candidate_dep;
 1380|   125k|        uint64_t candidate_tiebreak{0};
 1381|   125k|        bool have_any = false;
 1382|       |        // Iterate over all transactions.
 1383|   242k|        for (auto tx_idx : chunk_info.transactions) {
  ------------------
  |  Branch (1383:26): [True: 242k, False: 125k]
  ------------------
 1384|   242k|            const auto& tx_data = m_tx_data[tx_idx];
 1385|       |            // Iterate over all active child dependencies of the transaction.
 1386|   242k|            for (auto child_idx : tx_data.active_children) {
  ------------------
  |  Branch (1386:33): [True: 116k, False: 242k]
  ------------------
 1387|   116k|                const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
 1388|       |                // Skip if this dependency does not have equal top and bottom set feerates. Note
 1389|       |                // that the top cannot have higher feerate than the bottom, or OptimizeSteps would
 1390|       |                // have dealt with it.
 1391|   116k|                if (ByRatio{dep_top_info.feerate} < ByRatio{chunk_info.feerate}) continue;
  ------------------
  |  Branch (1391:21): [True: 16.5k, False: 99.5k]
  ------------------
 1392|  99.5k|                have_any = true;
 1393|       |                // Skip if this dependency does not have pivot in the right place.
 1394|  99.5k|                if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue;
  ------------------
  |  Branch (1394:21): [True: 47.0k, False: 52.4k]
  ------------------
 1395|       |                // Remember this as our chosen dependency if it has a better tiebreak.
 1396|  52.4k|                uint64_t tiebreak = m_rng.rand64() | 1;
 1397|  52.4k|                if (tiebreak > candidate_tiebreak) {
  ------------------
  |  Branch (1397:21): [True: 32.6k, False: 19.8k]
  ------------------
 1398|  32.6k|                    candidate_tiebreak = tiebreak;
 1399|  32.6k|                    candidate_dep = {tx_idx, child_idx};
 1400|  32.6k|                }
 1401|  52.4k|            }
 1402|   242k|        }
 1403|   125k|        m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count());
 1404|       |        // If no dependencies have equal top and bottom set feerate, this chunk is minimal.
 1405|   125k|        if (!have_any) return true;
  ------------------
  |  Branch (1405:13): [True: 96.7k, False: 29.1k]
  ------------------
 1406|       |        // If all found dependencies have the pivot in the wrong place, try moving it in the other
 1407|       |        // direction. If this was the second stage already, we are done.
 1408|  29.1k|        if (candidate_tiebreak == 0) {
  ------------------
  |  Branch (1408:13): [True: 6.51k, False: 22.6k]
  ------------------
 1409|       |            // Switch to other direction, and to second phase.
 1410|  6.51k|            flags ^= 3;
 1411|  6.51k|            if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags);
  ------------------
  |  Branch (1411:17): [True: 6.50k, False: 10]
  ------------------
 1412|  6.51k|            return true;
 1413|  6.51k|        }
 1414|       |
 1415|       |        // Otherwise, deactivate the dependency that was found.
 1416|  22.6k|        auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second);
 1417|       |        // Determine if there is a dependency from the new bottom to the new top (opposite from the
 1418|       |        // dependency that was just deactivated).
 1419|  22.6k|        auto& parent_reachable = m_reachable[parent_chunk_idx].first;
 1420|  22.6k|        auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
 1421|  22.6k|        if (parent_reachable.Overlaps(child_chunk_txn)) {
  ------------------
  |  Branch (1421:13): [True: 953, False: 21.7k]
  ------------------
 1422|       |            // A self-merge is needed. Note that the child_chunk_idx is the top, and
 1423|       |            // parent_chunk_idx is the bottom, because we activate a dependency in the reverse
 1424|       |            // direction compared to the deactivation above.
 1425|    953|            auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
 1426|       |            // Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx
 1427|       |            // will have changed.
 1428|    953|            m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags);
 1429|    953|            m_cost.MinimizeStepEnd(/*split=*/false);
 1430|  21.7k|        } else {
 1431|       |            // No self-merge happens, and thus we have found a way to split the chunk. Create two
 1432|       |            // smaller chunks, and add them to the queue. The one that contains the current pivot
 1433|       |            // gets to continue with it in the same direction, to minimize the number of times we
 1434|       |            // alternate direction. If we were in the second phase already, the newly created chunk
 1435|       |            // inherits that too, because we know no split with the pivot on the other side is
 1436|       |            // possible already. The new chunk without the current pivot gets a new randomly-chosen
 1437|       |            // one.
 1438|  21.7k|            if (move_pivot_down) {
  ------------------
  |  Branch (1438:17): [True: 10.6k, False: 11.0k]
  ------------------
 1439|  10.6k|                auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions);
 1440|  10.6k|                m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>());
 1441|  10.6k|                m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags);
 1442|  11.0k|            } else {
 1443|  11.0k|                auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions);
 1444|  11.0k|                m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags);
 1445|  11.0k|                m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>());
 1446|  11.0k|            }
 1447|  21.7k|            if (m_rng.randbool()) {
  ------------------
  |  Branch (1447:17): [True: 10.9k, False: 10.7k]
  ------------------
 1448|  10.9k|                std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]);
 1449|  10.9k|            }
 1450|  21.7k|            m_cost.MinimizeStepEnd(/*split=*/true);
 1451|  21.7k|        }
 1452|  22.6k|        return true;
 1453|  29.1k|    }
txgraph.cpp:_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEE3$_4EENS8_6vectorIjNS8_9allocatorIjEEEERKT_:
 1472|  2.67k|    {
 1473|  2.67k|        m_cost.GetLinearizationBegin();
 1474|       |        /** The output linearization. */
 1475|  2.67k|        std::vector<DepGraphIndex> ret;
 1476|  2.67k|        ret.reserve(m_set_info.size());
 1477|       |        /** A heap with all chunks (by set index) that can currently be included, sorted by
 1478|       |         *  chunk feerate (high to low), chunk size (small to large), and by least maximum element
 1479|       |         *  according to the fallback order (which is the second pair element). */
 1480|  2.67k|        std::array<std::pair<SetIdx, TxIdx>, SetType::Size()> ready_chunks;
 1481|       |        /** The number of entries of ready_chunks in use. */
 1482|  2.67k|        unsigned num_ready_chunks{0};
 1483|       |        /** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on
 1484|       |         *  other chunks (not including dependencies within the chunk itself). */
 1485|  2.67k|        std::array<TxIdx, SetType::Size()> chunk_deps;
 1486|  2.67k|        std::fill_n(chunk_deps.begin(), m_set_info.size(), TxIdx{0});
 1487|       |        /** For every transaction, indexed by TxIdx, the number of unmet dependencies the
 1488|       |         *  transaction has. */
 1489|  2.67k|        std::array<TxIdx, SetType::Size()> tx_deps;
 1490|  2.67k|        std::fill_n(tx_deps.begin(), m_tx_data.size(), TxIdx{0});
 1491|       |        /** A heap with all transactions within the current chunk that can be included, sorted by
 1492|       |         *  tx feerate (high to low), tx size (small to large), and fallback order. */
 1493|  2.67k|        std::array<TxIdx, SetType::Size()> ready_tx;
 1494|       |        /** The number of entries of ready_tx in use. */
 1495|  2.67k|        unsigned num_ready_tx{0};
 1496|       |        // Populate chunk_deps and tx_deps.
 1497|  2.67k|        unsigned num_deps{0};
 1498|   112k|        for (TxIdx chl_idx : m_transaction_idxs) {
  ------------------
  |  Branch (1498:28): [True: 112k, False: 2.67k]
  ------------------
 1499|   112k|            const auto& chl_data = m_tx_data[chl_idx];
 1500|   112k|            tx_deps[chl_idx] = chl_data.parents.Count();
 1501|   112k|            num_deps += tx_deps[chl_idx];
 1502|   112k|            auto chl_chunk_idx = chl_data.chunk_idx;
 1503|   112k|            auto& chl_chunk_info = m_set_info[chl_chunk_idx];
 1504|   112k|            chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count();
 1505|   112k|        }
 1506|       |        /** Function to compute the highest element of a chunk, by fallback_order. */
 1507|  2.67k|        auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept {
 1508|  2.67k|            auto& chunk = m_set_info[chunk_idx].transactions;
 1509|  2.67k|            auto it = chunk.begin();
 1510|  2.67k|            DepGraphIndex ret = *it;
 1511|  2.67k|            ++it;
 1512|  2.67k|            while (it != chunk.end()) {
 1513|  2.67k|                if (fallback_order(*it, ret) > 0) ret = *it;
 1514|  2.67k|                ++it;
 1515|  2.67k|            }
 1516|  2.67k|            return ret;
 1517|  2.67k|        };
 1518|       |        /** Comparison function for the transaction heap. Note that it is a max-heap, so
 1519|       |         *  tx_cmp_fn(a, b) == true means "a appears after b in the linearization". */
 1520|  2.67k|        auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1521|       |            // Bail out for identical transactions.
 1522|  2.67k|            if (a == b) return false;
 1523|       |            // First sort by increasing transaction feerate.
 1524|  2.67k|            auto& a_feerate = m_depgraph.FeeRate(a);
 1525|  2.67k|            auto& b_feerate = m_depgraph.FeeRate(b);
 1526|  2.67k|            auto feerate_cmp = ByRatio{a_feerate} <=> ByRatio{b_feerate};
 1527|  2.67k|            if (feerate_cmp != 0) return feerate_cmp < 0;
 1528|       |            // Then by decreasing transaction size.
 1529|  2.67k|            if (a_feerate.size != b_feerate.size) {
 1530|  2.67k|                return a_feerate.size > b_feerate.size;
 1531|  2.67k|            }
 1532|       |            // Tie-break by decreasing fallback_order.
 1533|  2.67k|            auto fallback_cmp = fallback_order(a, b);
 1534|  2.67k|            if (fallback_cmp != 0) return fallback_cmp > 0;
 1535|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1536|  2.67k|            Assume(false);
 1537|  2.67k|            return a < b;
 1538|  2.67k|        };
 1539|       |        // Construct a heap with all chunks that have no out-of-chunk dependencies.
 1540|       |        /** Comparison function for the chunk heap. Note that it is a max-heap, so
 1541|       |         *  chunk_cmp_fn(a, b) == true means "a appears after b in the linearization". */
 1542|  2.67k|        auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1543|       |            // Bail out for identical chunks.
 1544|  2.67k|            if (a.first == b.first) return false;
 1545|       |            // First sort by increasing chunk feerate.
 1546|  2.67k|            auto& chunk_feerate_a = m_set_info[a.first].feerate;
 1547|  2.67k|            auto& chunk_feerate_b = m_set_info[b.first].feerate;
 1548|  2.67k|            auto feerate_cmp = ByRatio{chunk_feerate_a} <=> ByRatio{chunk_feerate_b};
 1549|  2.67k|            if (feerate_cmp != 0) return feerate_cmp < 0;
 1550|       |            // Then by decreasing chunk size.
 1551|  2.67k|            if (chunk_feerate_a.size != chunk_feerate_b.size) {
 1552|  2.67k|                return chunk_feerate_a.size > chunk_feerate_b.size;
 1553|  2.67k|            }
 1554|       |            // Tie-break by decreasing fallback_order.
 1555|  2.67k|            auto fallback_cmp = fallback_order(a.second, b.second);
 1556|  2.67k|            if (fallback_cmp != 0) return fallback_cmp > 0;
 1557|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1558|  2.67k|            Assume(false);
 1559|  2.67k|            return a.second < b.second;
 1560|  2.67k|        };
 1561|       |        // Construct a heap with all chunks that have no out-of-chunk dependencies.
 1562|  96.7k|        for (SetIdx chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1562:31): [True: 96.7k, False: 2.67k]
  ------------------
 1563|  96.7k|            if (chunk_deps[chunk_idx] == 0) {
  ------------------
  |  Branch (1563:17): [True: 71.9k, False: 24.8k]
  ------------------
 1564|  71.9k|                ready_chunks[num_ready_chunks++] = {chunk_idx, max_fallback_fn(chunk_idx)};
 1565|  71.9k|            }
 1566|  96.7k|        }
 1567|  2.67k|        std::make_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1568|       |        // Pop chunks off the heap.
 1569|  99.4k|        while (num_ready_chunks > 0) {
  ------------------
  |  Branch (1569:16): [True: 96.7k, False: 2.67k]
  ------------------
 1570|  96.7k|            auto [chunk_idx, _rnd] = ready_chunks.front();
 1571|  96.7k|            std::pop_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1572|  96.7k|            --num_ready_chunks;
 1573|  96.7k|            Assume(chunk_deps[chunk_idx] == 0);
  ------------------
  |  |  128|  96.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1574|  96.7k|            const auto& chunk_txn = m_set_info[chunk_idx].transactions;
 1575|       |            // Build heap of all includable transactions in chunk.
 1576|  96.7k|            Assume(num_ready_tx == 0);
  ------------------
  |  |  128|  96.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1577|   112k|            for (TxIdx tx_idx : chunk_txn) {
  ------------------
  |  Branch (1577:31): [True: 112k, False: 96.7k]
  ------------------
 1578|   112k|                if (tx_deps[tx_idx] == 0) ready_tx[num_ready_tx++] = tx_idx;
  ------------------
  |  Branch (1578:21): [True: 100k, False: 12.8k]
  ------------------
 1579|   112k|            }
 1580|  96.7k|            Assume(num_ready_tx > 0);
  ------------------
  |  |  128|  96.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1581|  96.7k|            std::make_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1582|       |            // Pick transactions from the ready heap, append them to linearization, and decrement
 1583|       |            // dependency counts.
 1584|   209k|            while (num_ready_tx > 0) {
  ------------------
  |  Branch (1584:20): [True: 112k, False: 96.7k]
  ------------------
 1585|       |                // Pop an element from the tx_ready heap.
 1586|   112k|                auto tx_idx = ready_tx.front();
 1587|   112k|                std::pop_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1588|   112k|                --num_ready_tx;
 1589|       |                // Append to linearization.
 1590|   112k|                ret.push_back(tx_idx);
 1591|       |                // Decrement dependency counts.
 1592|   112k|                auto& tx_data = m_tx_data[tx_idx];
 1593|   112k|                for (TxIdx chl_idx : tx_data.children) {
  ------------------
  |  Branch (1593:36): [True: 51.7k, False: 112k]
  ------------------
 1594|  51.7k|                    auto& chl_data = m_tx_data[chl_idx];
 1595|       |                    // Decrement tx dependency count.
 1596|  51.7k|                    Assume(tx_deps[chl_idx] > 0);
  ------------------
  |  |  128|  51.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1597|  51.7k|                    if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) {
  ------------------
  |  Branch (1597:25): [True: 37.3k, False: 14.3k]
  |  Branch (1597:52): [True: 12.8k, False: 24.4k]
  ------------------
 1598|       |                        // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap.
 1599|  12.8k|                        ready_tx[num_ready_tx++] = chl_idx;
 1600|  12.8k|                        std::push_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1601|  12.8k|                    }
 1602|       |                    // Decrement chunk dependency count if this is out-of-chunk dependency.
 1603|  51.7k|                    if (chl_data.chunk_idx != chunk_idx) {
  ------------------
  |  Branch (1603:25): [True: 34.6k, False: 17.0k]
  ------------------
 1604|  34.6k|                        Assume(chunk_deps[chl_data.chunk_idx] > 0);
  ------------------
  |  |  128|  34.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1605|  34.6k|                        if (--chunk_deps[chl_data.chunk_idx] == 0) {
  ------------------
  |  Branch (1605:29): [True: 24.8k, False: 9.78k]
  ------------------
 1606|       |                            // Child chunk has no dependencies left. Add it to the chunk heap.
 1607|  24.8k|                            ready_chunks[num_ready_chunks++] = {chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)};
 1608|  24.8k|                            std::push_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1609|  24.8k|                        }
 1610|  34.6k|                    }
 1611|  51.7k|                }
 1612|   112k|            }
 1613|  96.7k|        }
 1614|  2.67k|        Assume(ret.size() == m_set_info.size());
  ------------------
  |  |  128|  2.67k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1615|  2.67k|        m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps);
 1616|  2.67k|        return ret;
 1617|  2.67k|    }
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEE3$_4EENS8_6vectorIjNS8_9allocatorIjEEEERKT_ENKUlhE_clEh:
 1507|  96.7k|        auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept {
 1508|  96.7k|            auto& chunk = m_set_info[chunk_idx].transactions;
 1509|  96.7k|            auto it = chunk.begin();
 1510|  96.7k|            DepGraphIndex ret = *it;
 1511|  96.7k|            ++it;
 1512|   112k|            while (it != chunk.end()) {
  ------------------
  |  Branch (1512:20): [True: 16.1k, False: 96.7k]
  ------------------
 1513|  16.1k|                if (fallback_order(*it, ret) > 0) ret = *it;
  ------------------
  |  Branch (1513:21): [True: 4.16k, False: 11.9k]
  ------------------
 1514|  16.1k|                ++it;
 1515|  16.1k|            }
 1516|  96.7k|            return ret;
 1517|  96.7k|        };
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEE3$_4EENS8_6vectorIjNS8_9allocatorIjEEEERKT_ENKUlSJ_RKT0_E0_clINS8_4pairIhjEESQ_EEDaSJ_SM_:
 1542|   775k|        auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1543|       |            // Bail out for identical chunks.
 1544|   775k|            if (a.first == b.first) return false;
  ------------------
  |  Branch (1544:17): [True: 0, False: 775k]
  ------------------
 1545|       |            // First sort by increasing chunk feerate.
 1546|   775k|            auto& chunk_feerate_a = m_set_info[a.first].feerate;
 1547|   775k|            auto& chunk_feerate_b = m_set_info[b.first].feerate;
 1548|   775k|            auto feerate_cmp = ByRatio{chunk_feerate_a} <=> ByRatio{chunk_feerate_b};
 1549|   775k|            if (feerate_cmp != 0) return feerate_cmp < 0;
  ------------------
  |  Branch (1549:17): [True: 186k, False: 589k]
  ------------------
 1550|       |            // Then by decreasing chunk size.
 1551|   589k|            if (chunk_feerate_a.size != chunk_feerate_b.size) {
  ------------------
  |  Branch (1551:17): [True: 94.5k, False: 494k]
  ------------------
 1552|  94.5k|                return chunk_feerate_a.size > chunk_feerate_b.size;
 1553|  94.5k|            }
 1554|       |            // Tie-break by decreasing fallback_order.
 1555|   494k|            auto fallback_cmp = fallback_order(a.second, b.second);
 1556|   494k|            if (fallback_cmp != 0) return fallback_cmp > 0;
  ------------------
  |  Branch (1556:17): [True: 494k, False: 0]
  ------------------
 1557|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1558|      0|            Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1559|      0|            return a.second < b.second;
 1560|   494k|        };
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail14MultiIntBitSetImLj2EEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEE3$_4EENS8_6vectorIjNS8_9allocatorIjEEEERKT_ENKUlSJ_RKT0_E_clIjjEEDaSJ_SM_:
 1520|  28.3k|        auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1521|       |            // Bail out for identical transactions.
 1522|  28.3k|            if (a == b) return false;
  ------------------
  |  Branch (1522:17): [True: 0, False: 28.3k]
  ------------------
 1523|       |            // First sort by increasing transaction feerate.
 1524|  28.3k|            auto& a_feerate = m_depgraph.FeeRate(a);
 1525|  28.3k|            auto& b_feerate = m_depgraph.FeeRate(b);
 1526|  28.3k|            auto feerate_cmp = ByRatio{a_feerate} <=> ByRatio{b_feerate};
 1527|  28.3k|            if (feerate_cmp != 0) return feerate_cmp < 0;
  ------------------
  |  Branch (1527:17): [True: 8.37k, False: 19.9k]
  ------------------
 1528|       |            // Then by decreasing transaction size.
 1529|  19.9k|            if (a_feerate.size != b_feerate.size) {
  ------------------
  |  Branch (1529:17): [True: 4.56k, False: 15.4k]
  ------------------
 1530|  4.56k|                return a_feerate.size > b_feerate.size;
 1531|  4.56k|            }
 1532|       |            // Tie-break by decreasing fallback_order.
 1533|  15.4k|            auto fallback_cmp = fallback_order(a, b);
 1534|  15.4k|            if (fallback_cmp != 0) return fallback_cmp > 0;
  ------------------
  |  Branch (1534:17): [True: 15.4k, False: 0]
  ------------------
 1535|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1536|      0|            Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1537|      0|            return a < b;
 1538|  15.4k|        };
_ZN17cluster_linearize13PostLinearizeIN13bitset_detail14MultiIntBitSetImLj2EEEEEvRKNS_8DepGraphIT_EENSt3__14spanIjLm18446744073709551615EEE:
 1873|  2.67k|{
 1874|       |    // This algorithm performs a number of passes (currently 2); the even ones operate from back to
 1875|       |    // front, the odd ones from front to back. Each results in an equal-or-better linearization
 1876|       |    // than the one started from.
 1877|       |    // - One pass in either direction guarantees that the resulting chunks are connected.
 1878|       |    // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
 1879|       |    //   guarantee this for graphs where each transaction has at most one child; backward passes
 1880|       |    //   guarantee this for graphs where each transaction has at most one parent).
 1881|       |    // - Starting with a backward pass guarantees the moved-tree property.
 1882|       |    //
 1883|       |    // During an odd (forward) pass, the high-level operation is:
 1884|       |    // - Start with an empty list of groups L=[].
 1885|       |    // - For every transaction i in the old linearization, from front to back:
 1886|       |    //   - Append a new group C=[i], containing just i, to the back of L.
 1887|       |    //   - While L has at least one group before C, and the group immediately before C has feerate
 1888|       |    //     lower than C:
 1889|       |    //     - If C depends on P:
 1890|       |    //       - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
 1891|       |    //     - Otherwise:
 1892|       |    //       - Swap P with C, continuing with the now-moved C.
 1893|       |    // - The output linearization is the concatenation of the groups in L.
 1894|       |    //
 1895|       |    // During even (backward) passes, i iterates from the back to the front of the existing
 1896|       |    // linearization, and new groups are prepended instead of appended to the list L. To enable
 1897|       |    // more code reuse, both passes append groups, but during even passes the meanings of
 1898|       |    // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
 1899|       |    // on output.
 1900|       |    //
 1901|       |    // In the implementation below, the groups are represented by singly-linked lists (pointing
 1902|       |    // from the back to the front), which are themselves organized in a singly-linked circular
 1903|       |    // list (each group pointing to its predecessor, with a special sentinel group at the front
 1904|       |    // that points back to the last group).
 1905|       |    //
 1906|       |    // Information about transaction t is stored in entries[t + 1], while the sentinel is in
 1907|       |    // entries[0].
 1908|       |
 1909|       |    /** Index of the sentinel in the entries array below. */
 1910|  2.67k|    static constexpr DepGraphIndex SENTINEL{0};
 1911|       |    /** Indicator that a group has no previous transaction. */
 1912|  2.67k|    static constexpr DepGraphIndex NO_PREV_TX{0};
 1913|       |
 1914|       |
 1915|       |    /** Data structure per transaction entry. */
 1916|  2.67k|    struct TxEntry
 1917|  2.67k|    {
 1918|       |        /** The index of the previous transaction in this group; NO_PREV_TX if this is the first
 1919|       |         *  entry of a group. */
 1920|  2.67k|        DepGraphIndex prev_tx;
 1921|       |
 1922|       |        // The fields below are only used for transactions that are the last one in a group
 1923|       |        // (referred to as tail transactions below).
 1924|       |
 1925|       |        /** Index of the first transaction in this group, possibly itself. */
 1926|  2.67k|        DepGraphIndex first_tx;
 1927|       |        /** Index of the last transaction in the previous group. The first group (the sentinel)
 1928|       |         *  points back to the last group here, making it a singly-linked circular list. */
 1929|  2.67k|        DepGraphIndex prev_group;
 1930|       |        /** All transactions in the group. Empty for the sentinel. */
 1931|  2.67k|        SetType group;
 1932|       |        /** All dependencies of the group (descendants in even passes; ancestors in odd ones). */
 1933|  2.67k|        SetType deps;
 1934|       |        /** The combined fee/size of transactions in the group. Fee is negated in even passes. */
 1935|  2.67k|        FeeFrac feerate;
 1936|  2.67k|    };
 1937|       |
 1938|       |    // As an example, consider the state corresponding to the linearization [1,0,3,2], with
 1939|       |    // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
 1940|       |    //
 1941|       |    //                                        +-----+
 1942|       |    //                                 0<-P-- | 0 S | ---\     Legend:
 1943|       |    //                                        +-----+    |
 1944|       |    //                                           ^       |     - digit in box: entries index
 1945|       |    //             /--------------F---------+    G       |       (note: one more than tx value)
 1946|       |    //             v                         \   |       |     - S: sentinel group
 1947|       |    //          +-----+        +-----+        +-----+    |          (empty feerate)
 1948|       |    //   0<-P-- | 2   | <--P-- | 1   | <--P-- | 4 T |    |     - T: tail transaction, contains
 1949|       |    //          +-----+        +-----+        +-----+    |          fields beyond prev_tv.
 1950|       |    //                                           ^       |     - P: prev_tx reference
 1951|       |    //                                           G       G     - F: first_tx reference
 1952|       |    //                                           |       |     - G: prev_group reference
 1953|       |    //                                        +-----+    |
 1954|       |    //                                 0<-P-- | 3 T | <--/
 1955|       |    //                                        +-----+
 1956|       |    //                                         ^   |
 1957|       |    //                                         \-F-/
 1958|       |    //
 1959|       |    // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
 1960|       |    // groups [2] and [3,0,1].
 1961|       |
 1962|  2.67k|    std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
 1963|       |
 1964|       |    // Perform two passes over the linearization.
 1965|  8.01k|    for (int pass = 0; pass < 2; ++pass) {
  ------------------
  |  Branch (1965:24): [True: 5.34k, False: 2.67k]
  ------------------
 1966|  5.34k|        int rev = !(pass & 1);
 1967|       |        // Construct a sentinel group, identifying the start of the list.
 1968|  5.34k|        entries[SENTINEL].prev_group = SENTINEL;
 1969|  5.34k|        Assume(entries[SENTINEL].feerate.IsEmpty());
  ------------------
  |  |  128|  5.34k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1970|       |
 1971|       |        // Iterate over all elements in the existing linearization.
 1972|   231k|        for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
  ------------------
  |  Branch (1972:35): [True: 225k, False: 5.34k]
  ------------------
 1973|       |            // Even passes are from back to front; odd passes from front to back.
 1974|   225k|            DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
  ------------------
  |  Branch (1974:47): [True: 112k, False: 112k]
  ------------------
 1975|       |            // Construct a new group containing just idx. In even passes, the meaning of
 1976|       |            // parent/child and high/low feerate are swapped.
 1977|   225k|            DepGraphIndex cur_group = idx + 1;
 1978|   225k|            entries[cur_group].group = SetType::Singleton(idx);
 1979|   225k|            entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
  ------------------
  |  Branch (1979:39): [True: 112k, False: 112k]
  ------------------
 1980|   225k|            entries[cur_group].feerate = depgraph.FeeRate(idx);
 1981|   225k|            if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
  ------------------
  |  Branch (1981:17): [True: 112k, False: 112k]
  ------------------
 1982|   225k|            entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
 1983|   225k|            entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
 1984|       |            // Insert the new group at the back of the groups linked list.
 1985|   225k|            entries[cur_group].prev_group = entries[SENTINEL].prev_group;
 1986|   225k|            entries[SENTINEL].prev_group = cur_group;
 1987|       |
 1988|       |            // Start merge/swap cycle.
 1989|   225k|            DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
 1990|   225k|            DepGraphIndex prev_group = entries[cur_group].prev_group;
 1991|       |            // Continue as long as the current group has higher feerate than the previous one.
 1992|   259k|            while (ByRatio{entries[cur_group].feerate} > ByRatio{entries[prev_group].feerate}) {
  ------------------
  |  Branch (1992:20): [True: 34.0k, False: 225k]
  ------------------
 1993|       |                // prev_group/cur_group/next_group refer to (the last transactions of) 3
 1994|       |                // consecutive entries in groups list.
 1995|  34.0k|                Assume(cur_group == entries[next_group].prev_group);
  ------------------
  |  |  128|  34.0k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1996|  34.0k|                Assume(prev_group == entries[cur_group].prev_group);
  ------------------
  |  |  128|  34.0k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1997|       |                // The sentinel has empty feerate, which is neither higher or lower than other
 1998|       |                // feerates. Thus, the while loop we are in here guarantees that cur_group and
 1999|       |                // prev_group are not the sentinel.
 2000|  34.0k|                Assume(cur_group != SENTINEL);
  ------------------
  |  |  128|  34.0k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2001|  34.0k|                Assume(prev_group != SENTINEL);
  ------------------
  |  |  128|  34.0k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2002|  34.0k|                if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
  ------------------
  |  Branch (2002:21): [True: 32.2k, False: 1.75k]
  ------------------
 2003|       |                    // There is a dependency between cur_group and prev_group; merge prev_group
 2004|       |                    // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
 2005|       |                    // but become unused.
 2006|  32.2k|                    entries[cur_group].group |= entries[prev_group].group;
 2007|  32.2k|                    entries[cur_group].deps |= entries[prev_group].deps;
 2008|  32.2k|                    entries[cur_group].feerate += entries[prev_group].feerate;
 2009|       |                    // Make the first of the current group point to the tail of the previous group.
 2010|  32.2k|                    entries[entries[cur_group].first_tx].prev_tx = prev_group;
 2011|       |                    // The first of the previous group becomes the first of the newly-merged group.
 2012|  32.2k|                    entries[cur_group].first_tx = entries[prev_group].first_tx;
 2013|       |                    // The previous group becomes whatever group was before the former one.
 2014|  32.2k|                    prev_group = entries[prev_group].prev_group;
 2015|  32.2k|                    entries[cur_group].prev_group = prev_group;
 2016|  32.2k|                } else {
 2017|       |                    // There is no dependency between cur_group and prev_group; swap them.
 2018|  1.75k|                    DepGraphIndex preprev_group = entries[prev_group].prev_group;
 2019|       |                    // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
 2020|       |                    // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
 2021|  1.75k|                    entries[next_group].prev_group = prev_group;
 2022|  1.75k|                    entries[prev_group].prev_group = cur_group;
 2023|  1.75k|                    entries[cur_group].prev_group = preprev_group;
 2024|       |                    // The current group remains the same, but the groups before/after it have
 2025|       |                    // changed.
 2026|  1.75k|                    next_group = prev_group;
 2027|  1.75k|                    prev_group = preprev_group;
 2028|  1.75k|                }
 2029|  34.0k|            }
 2030|   225k|        }
 2031|       |
 2032|       |        // Convert the entries back to linearization (overwriting the existing one).
 2033|  5.34k|        DepGraphIndex cur_group = entries[0].prev_group;
 2034|  5.34k|        DepGraphIndex done = 0;
 2035|   198k|        while (cur_group != SENTINEL) {
  ------------------
  |  Branch (2035:16): [True: 193k, False: 5.34k]
  ------------------
 2036|   193k|            DepGraphIndex cur_tx = cur_group;
 2037|       |            // Traverse the transactions of cur_group (from back to front), and write them in the
 2038|       |            // same order during odd passes, and reversed (front to back) in even passes.
 2039|   193k|            if (rev) {
  ------------------
  |  Branch (2039:17): [True: 96.7k, False: 96.7k]
  ------------------
 2040|   112k|                do {
 2041|   112k|                    *(linearization.begin() + (done++)) = cur_tx - 1;
 2042|   112k|                    cur_tx = entries[cur_tx].prev_tx;
 2043|   112k|                } while (cur_tx != NO_PREV_TX);
  ------------------
  |  Branch (2043:26): [True: 16.1k, False: 96.7k]
  ------------------
 2044|  96.7k|            } else {
 2045|   112k|                do {
 2046|   112k|                    *(linearization.end() - (++done)) = cur_tx - 1;
 2047|   112k|                    cur_tx = entries[cur_tx].prev_tx;
 2048|   112k|                } while (cur_tx != NO_PREV_TX);
  ------------------
  |  Branch (2048:26): [True: 16.1k, False: 96.7k]
  ------------------
 2049|  96.7k|            }
 2050|   193k|            cur_group = entries[cur_group].prev_group;
 2051|   193k|        }
 2052|  5.34k|        Assume(done == linearization.size());
  ------------------
  |  |  128|  5.34k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2053|  5.34k|    }
 2054|  2.67k|}
_ZN17cluster_linearize22ChunkLinearizationInfoIN13bitset_detail14MultiIntBitSetImLj2EEEEENSt3__16vectorINS_7SetInfoIT_EENS4_9allocatorIS8_EEEERKNS_8DepGraphIS7_EENS4_4spanIKjLm18446744073709551615EEE:
  430|   740k|{
  431|   740k|    std::vector<SetInfo<SetType>> ret;
  432|  13.7M|    for (DepGraphIndex i : linearization) {
  ------------------
  |  Branch (432:26): [True: 13.7M, False: 740k]
  ------------------
  433|       |        /** The new chunk to be added, initially a singleton. */
  434|  13.7M|        SetInfo<SetType> new_chunk(depgraph, i);
  435|       |        // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
  436|  19.4M|        while (!ret.empty() && ByRatio{new_chunk.feerate} > ByRatio{ret.back().feerate}) {
  ------------------
  |  Branch (436:16): [True: 18.4M, False: 1.00M]
  |  Branch (436:16): [True: 5.72M, False: 13.7M]
  |  Branch (436:32): [True: 5.72M, False: 12.7M]
  ------------------
  437|  5.72M|            new_chunk |= ret.back();
  438|  5.72M|            ret.pop_back();
  439|  5.72M|        }
  440|       |        // Actually move that new chunk into the chunking.
  441|  13.7M|        ret.emplace_back(std::move(new_chunk));
  442|  13.7M|    }
  443|   740k|    return ret;
  444|   740k|}
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE13PositionRangeEv:
  119|  8.01k|    DepGraphIndex PositionRange() const noexcept { return entries.size(); }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE17GetReducedParentsEj:
  212|   225k|    {
  213|   225k|        SetType parents = Ancestors(i);
  214|   225k|        parents.Reset(i);
  215|   259k|        for (auto parent : parents) {
  ------------------
  |  Branch (215:26): [True: 259k, False: 225k]
  ------------------
  216|   259k|            if (parents[parent]) {
  ------------------
  |  Branch (216:17): [True: 165k, False: 94.2k]
  ------------------
  217|   165k|                parents -= Ancestors(parent);
  218|   165k|                parents.Set(parent);
  219|   165k|            }
  220|   259k|        }
  221|   225k|        return parents;
  222|   225k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail14MultiIntBitSetImLj2EEEE7FeeRateERKS3_:
  250|  5.51k|    {
  251|  5.51k|        FeeFrac ret;
  252|  63.9k|        for (auto pos : elems) ret += entries[pos].feerate;
  ------------------
  |  Branch (252:23): [True: 63.9k, False: 5.51k]
  ------------------
  253|  5.51k|        return ret;
  254|  5.51k|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEEC2Ev:
   71|   302k|    DepGraph() noexcept = default;
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE18DynamicMemoryUsageEv:
  355|   623k|    {
  356|   623k|        return memusage::DynamicUsage(entries);
  357|   623k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE13PositionRangeEv:
  119|   670k|    DepGraphIndex PositionRange() const noexcept { return entries.size(); }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE7FeeRateEj:
  123|  23.8M|    const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE14AddTransactionERK7FeeFrac:
  137|  2.22M|    {
  138|  2.22M|        static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
  139|  2.22M|        auto available = ALL_POSITIONS - m_used;
  140|  2.22M|        Assume(available.Any());
  ------------------
  |  |  128|  2.22M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  141|  2.22M|        DepGraphIndex new_idx = available.First();
  142|  2.22M|        if (new_idx == entries.size()) {
  ------------------
  |  Branch (142:13): [True: 2.22M, False: 0]
  ------------------
  143|  2.22M|            entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
  144|  2.22M|        } else {
  145|      0|            entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
  146|      0|        }
  147|  2.22M|        m_used.Set(new_idx);
  148|  2.22M|        return new_idx;
  149|  2.22M|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE5EntryC2ERK7FeeFracRKS3_SA_:
   49|  2.22M|        Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE15AddDependenciesERKS3_j:
  181|  3.38M|    {
  182|  3.38M|        Assume(m_used[child]);
  ------------------
  |  |  128|  3.38M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  183|  3.38M|        Assume(parents.IsSubsetOf(m_used));
  ------------------
  |  |  128|  3.38M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  184|       |        // Compute the ancestors of parents that are not already ancestors of child.
  185|  3.38M|        SetType par_anc;
  186|  3.38M|        for (auto par : parents - Ancestors(child)) {
  ------------------
  |  Branch (186:23): [True: 2.62M, False: 3.38M]
  ------------------
  187|  2.62M|            par_anc |= Ancestors(par);
  188|  2.62M|        }
  189|  3.38M|        par_anc -= Ancestors(child);
  190|       |        // Bail out if there are no such ancestors.
  191|  3.38M|        if (par_anc.None()) return;
  ------------------
  |  Branch (191:13): [True: 1.71M, False: 1.66M]
  ------------------
  192|       |        // To each such ancestor, add as descendants the descendants of the child.
  193|  1.66M|        const auto& chl_des = entries[child].descendants;
  194|  6.77M|        for (auto anc_of_par : par_anc) {
  ------------------
  |  Branch (194:30): [True: 6.77M, False: 1.66M]
  ------------------
  195|  6.77M|            entries[anc_of_par].descendants |= chl_des;
  196|  6.77M|        }
  197|       |        // To each descendant of the child, add those ancestors.
  198|  2.83M|        for (auto dec_of_chl : Descendants(child)) {
  ------------------
  |  Branch (198:30): [True: 2.83M, False: 1.66M]
  ------------------
  199|  2.83M|            entries[dec_of_chl].ancestors |= par_anc;
  200|  2.83M|        }
  201|  1.66M|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE9AncestorsEj:
  127|  30.4M|    const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE11DescendantsEj:
  129|  8.59M|    const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE7FeeRateEj:
  125|  1.37M|    FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE17GetReducedParentsEj:
  212|  3.28M|    {
  213|  3.28M|        SetType parents = Ancestors(i);
  214|  3.28M|        parents.Reset(i);
  215|  13.9M|        for (auto parent : parents) {
  ------------------
  |  Branch (215:26): [True: 13.9M, False: 3.28M]
  ------------------
  216|  13.9M|            if (parents[parent]) {
  ------------------
  |  Branch (216:17): [True: 10.3M, False: 3.59M]
  ------------------
  217|  10.3M|                parents -= Ancestors(parent);
  218|  10.3M|                parents.Set(parent);
  219|  10.3M|            }
  220|  13.9M|        }
  221|  3.28M|        return parents;
  222|  3.28M|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEEaSEOS4_:
   75|  86.7k|    DepGraph& operator=(DepGraph&&) noexcept = default;
_ZN17cluster_linearize22ChunkLinearizationInfoIN13bitset_detail9IntBitSetImEEEENSt3__16vectorINS_7SetInfoIT_EENS4_9allocatorIS8_EEEERKNS_8DepGraphIS7_EENS4_4spanIKjLm18446744073709551615EEE:
  430|   278k|{
  431|   278k|    std::vector<SetInfo<SetType>> ret;
  432|  3.22M|    for (DepGraphIndex i : linearization) {
  ------------------
  |  Branch (432:26): [True: 3.22M, False: 278k]
  ------------------
  433|       |        /** The new chunk to be added, initially a singleton. */
  434|  3.22M|        SetInfo<SetType> new_chunk(depgraph, i);
  435|       |        // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
  436|  4.58M|        while (!ret.empty() && ByRatio{new_chunk.feerate} > ByRatio{ret.back().feerate}) {
  ------------------
  |  Branch (436:16): [True: 4.17M, False: 410k]
  |  Branch (436:16): [True: 1.36M, False: 3.22M]
  |  Branch (436:32): [True: 1.36M, False: 2.80M]
  ------------------
  437|  1.36M|            new_chunk |= ret.back();
  438|  1.36M|            ret.pop_back();
  439|  1.36M|        }
  440|       |        // Actually move that new chunk into the chunking.
  441|  3.22M|        ret.emplace_back(std::move(new_chunk));
  442|  3.22M|    }
  443|   278k|    return ret;
  444|   278k|}
_ZN17cluster_linearize7SetInfoIN13bitset_detail9IntBitSetImEEEC2ERKNS_8DepGraphIS3_EEj:
  377|  5.76M|        transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
_ZN17cluster_linearize7SetInfoIN13bitset_detail9IntBitSetImEEEoRERKS4_:
  393|  7.07M|    {
  394|  7.07M|        Assume(!transactions.Overlaps(other.transactions));
  ------------------
  |  |  128|  7.07M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  395|  7.07M|        transactions |= other.transactions;
  396|  7.07M|        feerate += other.feerate;
  397|  7.07M|        return *this;
  398|  7.07M|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEEaSERKS4_:
   74|  18.6k|    DepGraph& operator=(const DepGraph&) noexcept = default;
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE7CompactEv:
  350|   330k|    {
  351|   330k|        entries.shrink_to_fit();
  352|   330k|    }
_ZN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE18RemoveTransactionsERKS3_:
  161|  77.7k|    {
  162|  77.7k|        m_used -= del;
  163|       |        // Remove now-unused trailing entries.
  164|   163k|        while (!entries.empty() && !m_used[entries.size() - 1]) {
  ------------------
  |  Branch (164:16): [True: 148k, False: 14.9k]
  |  Branch (164:36): [True: 85.8k, False: 62.8k]
  ------------------
  165|  85.8k|            entries.pop_back();
  166|  85.8k|        }
  167|       |        // Remove the deleted transactions from ancestors/descendants of other transactions. Note
  168|       |        // that the deleted positions will retain old feerate and dependency information. This does
  169|       |        // not matter as they will be overwritten by AddTransaction if they get used again.
  170|   860k|        for (auto& entry : entries) {
  ------------------
  |  Branch (170:26): [True: 860k, False: 77.7k]
  ------------------
  171|   860k|            entry.ancestors &= m_used;
  172|   860k|            entry.descendants &= m_used;
  173|   860k|        }
  174|  77.7k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE9PositionsEv:
  117|   685k|    const SetType& Positions() const noexcept { return m_used; }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE22FindConnectedComponentERKS3_:
  292|   451k|    {
  293|   451k|        if (todo.None()) return todo;
  ------------------
  |  Branch (293:13): [True: 0, False: 451k]
  ------------------
  294|   451k|        return GetConnectedComponent(todo, todo.First());
  295|   451k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE21GetConnectedComponentERKS3_j:
  267|   451k|    {
  268|   451k|        Assume(todo[tx]);
  ------------------
  |  |  128|   451k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  269|   451k|        Assume(todo.IsSubsetOf(m_used));
  ------------------
  |  |  128|   451k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  270|   451k|        auto to_add = SetType::Singleton(tx);
  271|   451k|        SetType ret;
  272|  1.07M|        do {
  273|  1.07M|            SetType old = ret;
  274|  3.26M|            for (auto add : to_add) {
  ------------------
  |  Branch (274:27): [True: 3.26M, False: 1.07M]
  ------------------
  275|  3.26M|                ret |= Descendants(add);
  276|  3.26M|                ret |= Ancestors(add);
  277|  3.26M|            }
  278|  1.07M|            ret &= todo;
  279|  1.07M|            to_add = ret - old;
  280|  1.07M|        } while (to_add.Any());
  ------------------
  |  Branch (280:18): [True: 626k, False: 451k]
  ------------------
  281|   451k|        return ret;
  282|   451k|    }
txgraph.cpp:_ZN17cluster_linearize9LinearizeIN13bitset_detail9IntBitSetImEETkNS_16StrongComparatorIjEEZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS5_11TxGraphImplEimE3$_0EENSt3__15tupleIJNSA_6vectorIjNSA_9allocatorIjEEEEbmEEERKNS_8DepGraphIT_EEmmRKT0_NSA_4spanIKjLm18446744073709551615EEEb:
 1823|   224k|{
 1824|       |    /** Initialize a spanning forest data structure for this cluster. */
 1825|   224k|    SpanningForestState forest(depgraph, rng_seed);
 1826|   224k|    if (!old_linearization.empty()) {
  ------------------
  |  Branch (1826:9): [True: 224k, False: 0]
  ------------------
 1827|   224k|        forest.LoadLinearization(old_linearization);
 1828|   224k|        if (!is_topological) forest.MakeTopological();
  ------------------
  |  Branch (1828:13): [True: 205k, False: 18.9k]
  ------------------
 1829|   224k|    } else {
 1830|      0|        forest.MakeTopological();
 1831|      0|    }
 1832|       |    // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
 1833|       |    // is found.
 1834|   224k|    if (forest.GetCost() < max_cost) {
  ------------------
  |  Branch (1834:9): [True: 91.4k, False: 133k]
  ------------------
 1835|  91.4k|        forest.StartOptimizing();
 1836|   234k|        do {
 1837|   234k|            if (!forest.OptimizeStep()) break;
  ------------------
  |  Branch (1837:17): [True: 89.1k, False: 145k]
  ------------------
 1838|   234k|        } while (forest.GetCost() < max_cost);
  ------------------
  |  Branch (1838:18): [True: 143k, False: 2.31k]
  ------------------
 1839|  91.4k|    }
 1840|       |    // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
 1841|       |    // minimal.
 1842|   224k|    bool optimal = false;
 1843|   224k|    if (forest.GetCost() < max_cost) {
  ------------------
  |  Branch (1843:9): [True: 87.3k, False: 137k]
  ------------------
 1844|  87.3k|        forest.StartMinimizing();
 1845|   692k|        do {
 1846|   692k|            if (!forest.MinimizeStep()) {
  ------------------
  |  Branch (1846:17): [True: 75.6k, False: 616k]
  ------------------
 1847|  75.6k|                optimal = true;
 1848|  75.6k|                break;
 1849|  75.6k|            }
 1850|   692k|        } while (forest.GetCost() < max_cost);
  ------------------
  |  Branch (1850:18): [True: 605k, False: 11.6k]
  ------------------
 1851|  87.3k|    }
 1852|   224k|    return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()};
 1853|   224k|}
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEEC2ERKNS_8DepGraphIS3_EEmRKS4_:
 1183|   224k|        m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost)
 1184|   224k|    {
 1185|   224k|        m_cost.InitializeBegin();
 1186|   224k|        m_transaction_idxs = depgraph.Positions();
 1187|   224k|        auto num_transactions = m_transaction_idxs.Count();
 1188|   224k|        m_tx_data.resize(depgraph.PositionRange());
 1189|   224k|        m_set_info.resize(num_transactions);
 1190|   224k|        m_reachable.resize(num_transactions);
 1191|   224k|        m_suboptimal_chunks.reserve(num_transactions);
 1192|   224k|        size_t num_chunks = 0;
 1193|   224k|        size_t num_deps = 0;
 1194|  2.54M|        for (auto tx_idx : m_transaction_idxs) {
  ------------------
  |  Branch (1194:26): [True: 2.54M, False: 224k]
  ------------------
 1195|       |            // Fill in transaction data.
 1196|  2.54M|            auto& tx_data = m_tx_data[tx_idx];
 1197|  2.54M|            tx_data.parents = depgraph.GetReducedParents(tx_idx);
 1198|  2.61M|            for (auto parent_idx : tx_data.parents) {
  ------------------
  |  Branch (1198:34): [True: 2.61M, False: 2.54M]
  ------------------
 1199|  2.61M|                m_tx_data[parent_idx].children.Set(tx_idx);
 1200|  2.61M|            }
 1201|  2.54M|            num_deps += tx_data.parents.Count();
 1202|       |            // Create a singleton chunk for it.
 1203|  2.54M|            tx_data.chunk_idx = num_chunks;
 1204|  2.54M|            m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx);
 1205|  2.54M|        }
 1206|       |        // Set the reachable transactions for each chunk to the transactions' parents and children.
 1207|  2.76M|        for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) {
  ------------------
  |  Branch (1207:36): [True: 2.54M, False: 224k]
  ------------------
 1208|  2.54M|            auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()];
 1209|  2.54M|            m_reachable[chunk_idx].first = tx_data.parents;
 1210|  2.54M|            m_reachable[chunk_idx].second = tx_data.children;
 1211|  2.54M|        }
 1212|   224k|        Assume(num_chunks == num_transactions);
  ------------------
  |  |  128|   224k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1213|       |        // Mark all chunk sets as chunks.
 1214|   224k|        m_chunk_idxs = SetType::Fill(num_chunks);
 1215|   224k|        m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps);
 1216|   224k|    }
_ZN17cluster_linearize19SFLDefaultCostModel15InitializeBeginEv:
  502|   227k|    inline void InitializeBegin() noexcept {}
_ZN17cluster_linearize7SetInfoIN13bitset_detail9IntBitSetImEEEC2Ev:
  370|  2.54M|    SetInfo() noexcept = default;
_ZN17cluster_linearize19SFLDefaultCostModel13InitializeEndEii:
  504|   227k|    {
  505|       |         // Cost of initialization.
  506|   227k|         m_cost += 39 * num_txns;
  507|       |         // Cost of producing linearization at the end.
  508|   227k|         m_cost += 48 * num_txns + 4 * num_deps;
  509|   227k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE17LoadLinearizationENSt3__14spanIKjLm18446744073709551615EEE:
 1222|   224k|    {
 1223|       |        // Add transactions one by one, in order of existing linearization.
 1224|  2.54M|        for (DepGraphIndex tx_idx : old_linearization) {
  ------------------
  |  Branch (1224:35): [True: 2.54M, False: 224k]
  ------------------
 1225|  2.54M|            auto chunk_idx = m_tx_data[tx_idx].chunk_idx;
 1226|       |            // Merge the chunk upwards, as long as merging succeeds.
 1227|  4.37M|            while (true) {
  ------------------
  |  Branch (1227:20): [True: 4.37M, Folded]
  ------------------
 1228|  4.37M|                chunk_idx = MergeStep<false>(chunk_idx);
 1229|  4.37M|                if (chunk_idx == INVALID_SET_IDX) break;
  ------------------
  |  Branch (1229:21): [True: 2.54M, False: 1.83M]
  ------------------
 1230|  4.37M|            }
 1231|  2.54M|        }
 1232|   224k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE9MergeStepILb0EEEhh:
 1060|  4.71M|    {
 1061|  4.71M|        auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx);
 1062|  4.71M|        if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX;
  ------------------
  |  Branch (1062:13): [True: 2.87M, False: 1.84M]
  ------------------
 1063|  1.84M|        chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx);
 1064|  1.84M|        Assume(chunk_idx != INVALID_SET_IDX);
  ------------------
  |  |  128|  1.84M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1065|  1.84M|        return chunk_idx;
 1066|  4.71M|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE18PickMergeCandidateILb0EEEhh:
 1010|  4.71M|    {
 1011|  4.71M|        m_cost.PickMergeCandidateBegin();
 1012|       |        /** Information about the chunk. */
 1013|  4.71M|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|  4.71M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1014|  4.71M|        auto& chunk_info = m_set_info[chunk_idx];
 1015|       |        // Iterate over all chunks reachable from this one. For those depended-on chunks,
 1016|       |        // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
 1017|       |        // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
 1018|       |        // among them.
 1019|       |
 1020|       |        /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when
 1021|       |         *  looking for candidate chunks to merge with. Initially, this is the original chunk's
 1022|       |         *  feerate, but is updated to be the current best candidate whenever one is found. */
 1023|  4.71M|        FeeFrac best_other_chunk_feerate = chunk_info.feerate;
 1024|       |        /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */
 1025|  4.71M|        SetIdx best_other_chunk_idx = INVALID_SET_IDX;
 1026|       |        /** We generate random tiebreak values to pick between equal-feerate candidate chunks.
 1027|       |         *  This variable stores the tiebreak of the current best candidate. */
 1028|  4.71M|        uint64_t best_other_chunk_tiebreak{0};
 1029|       |
 1030|       |        /** Which parent/child transactions we still need to process the chunks for. */
 1031|  4.71M|        auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first;
  ------------------
  |  Branch (1031:21): [Folded, False: 4.71M]
  ------------------
 1032|  4.71M|        unsigned steps = 0;
 1033|  10.5M|        while (todo.Any()) {
  ------------------
  |  Branch (1033:16): [True: 5.85M, False: 4.71M]
  ------------------
 1034|  5.85M|            ++steps;
 1035|       |            // Find a chunk for a transaction in todo, and remove all its transactions from todo.
 1036|  5.85M|            auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx;
 1037|  5.85M|            auto& reached_chunk_info = m_set_info[reached_chunk_idx];
 1038|  5.85M|            todo -= reached_chunk_info.transactions;
 1039|       |            // See if it has an acceptable feerate.
 1040|  5.85M|            auto cmp = DownWard ? ByRatio{best_other_chunk_feerate} <=> ByRatio{reached_chunk_info.feerate}
  ------------------
  |  Branch (1040:24): [Folded, False: 5.85M]
  ------------------
 1041|  5.85M|                                : ByRatio{reached_chunk_info.feerate} <=> ByRatio{best_other_chunk_feerate};
 1042|  5.85M|            if (cmp > 0) continue;
  ------------------
  |  Branch (1042:17): [True: 3.28M, False: 2.56M]
  ------------------
 1043|  2.56M|            uint64_t tiebreak = m_rng.rand64();
 1044|  2.56M|            if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
  ------------------
  |  Branch (1044:17): [True: 849k, False: 1.71M]
  |  Branch (1044:28): [True: 1.38M, False: 329k]
  ------------------
 1045|  2.23M|                best_other_chunk_feerate = reached_chunk_info.feerate;
 1046|  2.23M|                best_other_chunk_idx = reached_chunk_idx;
 1047|  2.23M|                best_other_chunk_tiebreak = tiebreak;
 1048|  2.23M|            }
 1049|  2.56M|        }
 1050|  4.71M|        Assume(steps <= m_set_info.size());
  ------------------
  |  |  128|  4.71M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1051|       |
 1052|  4.71M|        m_cost.PickMergeCandidateEnd(/*num_steps=*/steps);
 1053|  4.71M|        return best_other_chunk_idx;
 1054|  4.71M|    }
_ZN17cluster_linearize19SFLDefaultCostModel23PickMergeCandidateBeginEv:
  532|  5.22M|    inline void PickMergeCandidateBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel21PickMergeCandidateEndEi:
  533|  5.22M|    inline void PickMergeCandidateEnd(int num_steps) noexcept { m_cost += 8 * num_steps; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE19MergeChunksDirectedILb0EEEhhh:
  999|  1.84M|    {
 1000|       |        if constexpr (DownWard) {
 1001|       |            return MergeChunks(chunk_idx, merge_chunk_idx);
 1002|  1.84M|        } else {
 1003|  1.84M|            return MergeChunks(merge_chunk_idx, chunk_idx);
 1004|  1.84M|        }
 1005|  1.84M|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE11MergeChunksEhh:
  951|  1.86M|    {
  952|  1.86M|        m_cost.MergeChunksBegin();
  953|  1.86M|        Assume(m_chunk_idxs[top_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  954|  1.86M|        Assume(m_chunk_idxs[bottom_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  955|  1.86M|        auto& top_chunk_info = m_set_info[top_idx];
  956|  1.86M|        auto& bottom_chunk_info = m_set_info[bottom_idx];
  957|       |        // Count the number of dependencies between bottom_chunk and top_chunk, remembering the
  958|       |        // per-transaction counts so the picking loop below does not need to recompute the
  959|       |        // intersections.
  960|  1.86M|        unsigned num_deps{0};
  961|  1.86M|        std::array<SetIdx, SetType::Size()> counts;
  962|  7.26M|        for (auto tx_idx : top_chunk_info.transactions) {
  ------------------
  |  Branch (962:26): [True: 7.26M, False: 1.86M]
  ------------------
  963|  7.26M|            auto& tx_data = m_tx_data[tx_idx];
  964|  7.26M|            auto count = (tx_data.children & bottom_chunk_info.transactions).Count();
  965|  7.26M|            counts[tx_idx] = count;
  966|  7.26M|            num_deps += count;
  967|  7.26M|        }
  968|  1.86M|        m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count());
  969|  1.86M|        Assume(num_deps > 0);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  970|       |        // Uniformly randomly pick one of them and activate it.
  971|  1.86M|        unsigned pick = m_rng.randrange(num_deps);
  972|  1.86M|        unsigned num_steps = 0;
  973|  4.97M|        for (auto tx_idx : top_chunk_info.transactions) {
  ------------------
  |  Branch (973:26): [True: 4.97M, False: 0]
  ------------------
  974|  4.97M|            ++num_steps;
  975|  4.97M|            auto count = counts[tx_idx];
  976|  4.97M|            if (pick < count) {
  ------------------
  |  Branch (976:17): [True: 1.86M, False: 3.10M]
  ------------------
  977|  1.86M|                auto& tx_data = m_tx_data[tx_idx];
  978|  1.86M|                auto intersect = tx_data.children & bottom_chunk_info.transactions;
  979|  1.86M|                for (auto child_idx : intersect) {
  ------------------
  |  Branch (979:37): [True: 1.86M, False: 0]
  ------------------
  980|  1.86M|                    if (pick == 0) {
  ------------------
  |  Branch (980:25): [True: 1.86M, False: 5.70k]
  ------------------
  981|  1.86M|                        m_cost.MergeChunksEnd(/*num_steps=*/num_steps);
  982|  1.86M|                        return Activate(tx_idx, child_idx);
  983|  1.86M|                    }
  984|  5.70k|                    --pick;
  985|  5.70k|                }
  986|      0|                Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  987|      0|                break;
  988|  1.86M|            }
  989|  3.10M|            pick -= count;
  990|  3.10M|        }
  991|      0|        Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  992|      0|        return INVALID_SET_IDX;
  993|  1.86M|    }
_ZN17cluster_linearize19SFLDefaultCostModel16MergeChunksBeginEv:
  529|  1.90M|    inline void MergeChunksBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel14MergeChunksMidEi:
  530|  1.90M|    inline void MergeChunksMid(int num_txns) noexcept { m_cost += 2 * num_txns; }
_ZN17cluster_linearize19SFLDefaultCostModel14MergeChunksEndEi:
  531|  1.90M|    inline void MergeChunksEnd(int num_steps) noexcept { m_cost += 3 * num_steps + 5; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE8ActivateEjj:
  819|  1.86M|    {
  820|  1.86M|        m_cost.ActivateBegin();
  821|       |        // Gather and check information about the parent and child transactions.
  822|  1.86M|        auto& parent_data = m_tx_data[parent_idx];
  823|  1.86M|        auto& child_data = m_tx_data[child_idx];
  824|  1.86M|        Assume(parent_data.children[child_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  825|  1.86M|        Assume(!parent_data.active_children[child_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  826|       |        // Get the set index of the chunks the parent and child are currently in. The parent chunk
  827|       |        // will become the top set of the newly activated dependency, while the child chunk will be
  828|       |        // grown to become the merged chunk.
  829|  1.86M|        auto parent_chunk_idx = parent_data.chunk_idx;
  830|  1.86M|        auto child_chunk_idx = child_data.chunk_idx;
  831|  1.86M|        Assume(parent_chunk_idx != child_chunk_idx);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  832|  1.86M|        Assume(m_chunk_idxs[parent_chunk_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  833|  1.86M|        Assume(m_chunk_idxs[child_chunk_idx]);
  ------------------
  |  |  128|  1.86M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  834|  1.86M|        auto& top_info = m_set_info[parent_chunk_idx];
  835|  1.86M|        auto& bottom_info = m_set_info[child_chunk_idx];
  836|       |
  837|       |        // Consider the following example:
  838|       |        //
  839|       |        //    A           A     There are two chunks, ABC and DEF, and the inactive E->C dependency
  840|       |        //   / \         / \    is activated, resulting in a single chunk ABCDEF.
  841|       |        //  B   C       B   C
  842|       |        //      :  ==>      |   Dependency | top set before | top set after | change
  843|       |        //  D   E       D   E   B->A       | AC             | ACDEF         | +DEF
  844|       |        //   \ /         \ /    C->A       | AB             | AB            |
  845|       |        //    F           F     F->D       | D              | D             |
  846|       |        //                      F->E       | E              | ABCE          | +ABC
  847|       |        //
  848|       |        // The common pattern here is that any dependency which has the parent or child of the
  849|       |        // dependency being activated (E->C here) in its top set, will have the opposite part added
  850|       |        // to it. This is true for B->A and F->E, but not for C->A and F->D.
  851|       |        //
  852|       |        // Traverse the old parent chunk top_info (ABC in example), and add bottom_info (DEF) to
  853|       |        // every dependency's top set which has the parent (C) in it. At the same time, change the
  854|       |        // chunk_idx for each to be child_chunk_idx, which becomes the set for the merged chunk.
  855|  7.26M|        for (auto tx_idx : top_info.transactions) {
  ------------------
  |  Branch (855:26): [True: 7.26M, False: 1.86M]
  ------------------
  856|  7.26M|            auto& tx_data = m_tx_data[tx_idx];
  857|  7.26M|            tx_data.chunk_idx = child_chunk_idx;
  858|  7.26M|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (858:37): [True: 5.39M, False: 7.26M]
  ------------------
  859|  5.39M|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  860|  5.39M|                if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info;
  ------------------
  |  Branch (860:21): [True: 2.37M, False: 3.02M]
  ------------------
  861|  5.39M|            }
  862|  7.26M|        }
  863|       |        // Traverse the old child chunk bottom_info (DEF in example), and add top_info (ABC) to
  864|       |        // every dependency's top set which has the child (E) in it.
  865|  5.30M|        for (auto tx_idx : bottom_info.transactions) {
  ------------------
  |  Branch (865:26): [True: 5.30M, False: 1.86M]
  ------------------
  866|  5.30M|            auto& tx_data = m_tx_data[tx_idx];
  867|  5.30M|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (867:37): [True: 3.44M, False: 5.30M]
  ------------------
  868|  3.44M|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  869|  3.44M|                if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info;
  ------------------
  |  Branch (869:21): [True: 1.47M, False: 1.97M]
  ------------------
  870|  3.44M|            }
  871|  5.30M|        }
  872|       |        // Merge top_info into bottom_info, which becomes the merged chunk.
  873|  1.86M|        bottom_info |= top_info;
  874|       |        // Compute merged sets of reachable transactions from the new chunk, based on the input
  875|       |        // chunks' reachable sets.
  876|  1.86M|        m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first;
  877|  1.86M|        m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second;
  878|  1.86M|        m_reachable[child_chunk_idx].first -= bottom_info.transactions;
  879|  1.86M|        m_reachable[child_chunk_idx].second -= bottom_info.transactions;
  880|       |        // Make parent chunk the set for the new active dependency.
  881|  1.86M|        parent_data.dep_top_idx[child_idx] = parent_chunk_idx;
  882|  1.86M|        parent_data.active_children.Set(child_idx);
  883|  1.86M|        m_chunk_idxs.Reset(parent_chunk_idx);
  884|       |        // Return the newly merged chunk.
  885|  1.86M|        m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1);
  886|  1.86M|        return child_chunk_idx;
  887|  1.86M|    }
_ZN17cluster_linearize19SFLDefaultCostModel13ActivateBeginEv:
  525|  1.90M|    inline void ActivateBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel11ActivateEndEi:
  526|  1.90M|    inline void ActivateEnd(int num_deps) noexcept { m_cost += 10 * num_deps + 1; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE15MakeTopologicalEv:
 1236|   205k|    {
 1237|   205k|        m_cost.MakeTopologicalBegin();
 1238|   205k|        Assume(m_suboptimal_chunks.empty());
  ------------------
  |  |  128|   205k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1239|       |        /** What direction to initially merge chunks in; one of the two directions is enough. This
 1240|       |         *  is sufficient because if a non-topological inactive dependency exists between two
 1241|       |         *  chunks, at least one of the two chunks will eventually be processed in a direction that
 1242|       |         *  discovers it - either the lower chunk tries upward, or the upper chunk tries downward.
 1243|       |         *  Chunks that are the result of the merging are always tried in both directions. */
 1244|   205k|        unsigned init_dir = m_rng.randbool();
 1245|       |        /** Which chunks are the result of merging, and thus need merge attempts in both
 1246|       |         *  directions. */
 1247|   205k|        SetType merged_chunks;
 1248|       |        // Mark chunks as suboptimal.
 1249|   205k|        m_suboptimal_idxs = m_chunk_idxs;
 1250|   633k|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1250:29): [True: 633k, False: 205k]
  ------------------
 1251|   633k|            m_suboptimal_chunks.emplace_back(chunk_idx);
 1252|       |            // Randomize the initial order of suboptimal chunks in the queue.
 1253|   633k|            SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());
 1254|   633k|            if (j != m_suboptimal_chunks.size() - 1) {
  ------------------
  |  Branch (1254:17): [True: 320k, False: 313k]
  ------------------
 1255|   320k|                std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);
 1256|   320k|            }
 1257|   633k|        }
 1258|   205k|        unsigned chunks = m_chunk_idxs.Count();
 1259|   205k|        unsigned steps = 0;
 1260|   844k|        while (!m_suboptimal_chunks.empty()) {
  ------------------
  |  Branch (1260:16): [True: 639k, False: 205k]
  ------------------
 1261|   639k|            ++steps;
 1262|       |            // Pop an entry from the potentially-suboptimal chunk queue.
 1263|   639k|            SetIdx chunk_idx = m_suboptimal_chunks.front();
 1264|   639k|            m_suboptimal_chunks.pop_front();
 1265|   639k|            Assume(m_suboptimal_idxs[chunk_idx]);
  ------------------
  |  |  128|   639k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1266|   639k|            m_suboptimal_idxs.Reset(chunk_idx);
 1267|       |            // If what was popped is not currently a chunk, continue. This may
 1268|       |            // happen when it was merged with something else since being added.
 1269|   639k|            if (!m_chunk_idxs[chunk_idx]) continue;
  ------------------
  |  Branch (1269:17): [True: 1.96k, False: 637k]
  ------------------
 1270|       |            /** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */
 1271|   637k|            unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1;
  ------------------
  |  Branch (1271:34): [True: 6.02k, False: 631k]
  ------------------
 1272|   637k|            int flip = m_rng.randbool();
 1273|  1.90M|            for (int i = 0; i < 2; ++i) {
  ------------------
  |  Branch (1273:29): [True: 1.27M, False: 630k]
  ------------------
 1274|  1.27M|                if (i ^ flip) {
  ------------------
  |  Branch (1274:21): [True: 635k, False: 635k]
  ------------------
 1275|   635k|                    if (!(direction & 1)) continue;
  ------------------
  |  Branch (1275:25): [True: 319k, False: 316k]
  ------------------
 1276|       |                    // Attempt to merge the chunk upwards.
 1277|   316k|                    auto result_up = MergeStep<false>(chunk_idx);
 1278|   316k|                    if (result_up != INVALID_SET_IDX) {
  ------------------
  |  Branch (1278:25): [True: 3.11k, False: 312k]
  ------------------
 1279|  3.11k|                        if (!m_suboptimal_idxs[result_up]) {
  ------------------
  |  Branch (1279:29): [True: 3.11k, False: 0]
  ------------------
 1280|  3.11k|                            m_suboptimal_idxs.Set(result_up);
 1281|  3.11k|                            m_suboptimal_chunks.push_back(result_up);
 1282|  3.11k|                        }
 1283|  3.11k|                        merged_chunks.Set(result_up);
 1284|  3.11k|                        break;
 1285|  3.11k|                    }
 1286|   635k|                } else {
 1287|   635k|                    if (!(direction & 2)) continue;
  ------------------
  |  Branch (1287:25): [True: 309k, False: 326k]
  ------------------
 1288|       |                    // Attempt to merge the chunk downwards.
 1289|   326k|                    auto result_down = MergeStep<true>(chunk_idx);
 1290|   326k|                    if (result_down != INVALID_SET_IDX) {
  ------------------
  |  Branch (1290:25): [True: 3.81k, False: 322k]
  ------------------
 1291|  3.81k|                        if (!m_suboptimal_idxs[result_down]) {
  ------------------
  |  Branch (1291:29): [True: 2.51k, False: 1.29k]
  ------------------
 1292|  2.51k|                            m_suboptimal_idxs.Set(result_down);
 1293|  2.51k|                            m_suboptimal_chunks.push_back(result_down);
 1294|  2.51k|                        }
 1295|  3.81k|                        merged_chunks.Set(result_down);
 1296|  3.81k|                        break;
 1297|  3.81k|                    }
 1298|   326k|                }
 1299|  1.27M|            }
 1300|   637k|        }
 1301|   205k|        m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps);
 1302|   205k|    }
_ZN17cluster_linearize19SFLDefaultCostModel20MakeTopologicalBeginEv:
  518|   205k|    inline void MakeTopologicalBegin() noexcept {}
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE9MergeStepILb1EEEhh:
 1060|   356k|    {
 1061|   356k|        auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx);
 1062|   356k|        if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX;
  ------------------
  |  Branch (1062:13): [True: 343k, False: 12.7k]
  ------------------
 1063|  12.7k|        chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx);
 1064|  12.7k|        Assume(chunk_idx != INVALID_SET_IDX);
  ------------------
  |  |  128|  12.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1065|  12.7k|        return chunk_idx;
 1066|   356k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE18PickMergeCandidateILb1EEEhh:
 1010|   356k|    {
 1011|   356k|        m_cost.PickMergeCandidateBegin();
 1012|       |        /** Information about the chunk. */
 1013|   356k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|   356k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1014|   356k|        auto& chunk_info = m_set_info[chunk_idx];
 1015|       |        // Iterate over all chunks reachable from this one. For those depended-on chunks,
 1016|       |        // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
 1017|       |        // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
 1018|       |        // among them.
 1019|       |
 1020|       |        /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when
 1021|       |         *  looking for candidate chunks to merge with. Initially, this is the original chunk's
 1022|       |         *  feerate, but is updated to be the current best candidate whenever one is found. */
 1023|   356k|        FeeFrac best_other_chunk_feerate = chunk_info.feerate;
 1024|       |        /** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */
 1025|   356k|        SetIdx best_other_chunk_idx = INVALID_SET_IDX;
 1026|       |        /** We generate random tiebreak values to pick between equal-feerate candidate chunks.
 1027|       |         *  This variable stores the tiebreak of the current best candidate. */
 1028|   356k|        uint64_t best_other_chunk_tiebreak{0};
 1029|       |
 1030|       |        /** Which parent/child transactions we still need to process the chunks for. */
 1031|   356k|        auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first;
  ------------------
  |  Branch (1031:21): [True: 356k, Folded]
  ------------------
 1032|   356k|        unsigned steps = 0;
 1033|   633k|        while (todo.Any()) {
  ------------------
  |  Branch (1033:16): [True: 277k, False: 356k]
  ------------------
 1034|   277k|            ++steps;
 1035|       |            // Find a chunk for a transaction in todo, and remove all its transactions from todo.
 1036|   277k|            auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx;
 1037|   277k|            auto& reached_chunk_info = m_set_info[reached_chunk_idx];
 1038|   277k|            todo -= reached_chunk_info.transactions;
 1039|       |            // See if it has an acceptable feerate.
 1040|   277k|            auto cmp = DownWard ? ByRatio{best_other_chunk_feerate} <=> ByRatio{reached_chunk_info.feerate}
  ------------------
  |  Branch (1040:24): [True: 277k, Folded]
  ------------------
 1041|   277k|                                : ByRatio{reached_chunk_info.feerate} <=> ByRatio{best_other_chunk_feerate};
 1042|   277k|            if (cmp > 0) continue;
  ------------------
  |  Branch (1042:17): [True: 260k, False: 16.9k]
  ------------------
 1043|  16.9k|            uint64_t tiebreak = m_rng.rand64();
 1044|  16.9k|            if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
  ------------------
  |  Branch (1044:17): [True: 5.86k, False: 11.1k]
  |  Branch (1044:28): [True: 9.00k, False: 2.11k]
  ------------------
 1045|  14.8k|                best_other_chunk_feerate = reached_chunk_info.feerate;
 1046|  14.8k|                best_other_chunk_idx = reached_chunk_idx;
 1047|  14.8k|                best_other_chunk_tiebreak = tiebreak;
 1048|  14.8k|            }
 1049|  16.9k|        }
 1050|   356k|        Assume(steps <= m_set_info.size());
  ------------------
  |  |  128|   356k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1051|       |
 1052|   356k|        m_cost.PickMergeCandidateEnd(/*num_steps=*/steps);
 1053|   356k|        return best_other_chunk_idx;
 1054|   356k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE19MergeChunksDirectedILb1EEEhhh:
  999|  12.7k|    {
 1000|  12.7k|        if constexpr (DownWard) {
 1001|  12.7k|            return MergeChunks(chunk_idx, merge_chunk_idx);
 1002|       |        } else {
 1003|       |            return MergeChunks(merge_chunk_idx, chunk_idx);
 1004|       |        }
 1005|  12.7k|    }
_ZN17cluster_linearize19SFLDefaultCostModel18MakeTopologicalEndEii:
  520|   205k|    {
  521|   205k|        m_cost += 20 * num_chunks + 28 * num_steps;
  522|   205k|    }
_ZNK17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE7GetCostEv:
 1643|  1.43M|    uint64_t GetCost() const noexcept { return m_cost.GetCost(); }
_ZNK17cluster_linearize19SFLDefaultCostModel7GetCostEv:
  544|  1.64M|    inline uint64_t GetCost() const noexcept { return m_cost; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE15StartOptimizingEv:
 1306|  91.4k|    {
 1307|  91.4k|        m_cost.StartOptimizingBegin();
 1308|  91.4k|        Assume(m_suboptimal_chunks.empty());
  ------------------
  |  |  128|  91.4k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1309|       |        // Mark chunks suboptimal.
 1310|  91.4k|        m_suboptimal_idxs = m_chunk_idxs;
 1311|   201k|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1311:29): [True: 201k, False: 91.4k]
  ------------------
 1312|   201k|            m_suboptimal_chunks.push_back(chunk_idx);
 1313|       |            // Randomize the initial order of suboptimal chunks in the queue.
 1314|   201k|            SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());
 1315|   201k|            if (j != m_suboptimal_chunks.size() - 1) {
  ------------------
  |  Branch (1315:17): [True: 75.0k, False: 126k]
  ------------------
 1316|  75.0k|                std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);
 1317|  75.0k|            }
 1318|   201k|        }
 1319|  91.4k|        m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size());
 1320|  91.4k|    }
_ZN17cluster_linearize19SFLDefaultCostModel20StartOptimizingBeginEv:
  523|  94.1k|    inline void StartOptimizingBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel18StartOptimizingEndEi:
  524|  94.1k|    inline void StartOptimizingEnd(int num_chunks) noexcept { m_cost += 13 * num_chunks; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE12OptimizeStepEv:
 1324|   234k|    {
 1325|   234k|        auto chunk_idx = PickChunkToOptimize();
 1326|   234k|        if (chunk_idx == INVALID_SET_IDX) {
  ------------------
  |  Branch (1326:13): [True: 0, False: 234k]
  ------------------
 1327|       |            // No improvable chunk was found, we are done.
 1328|      0|            return false;
 1329|      0|        }
 1330|   234k|        auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx);
 1331|   234k|        if (parent_idx == TxIdx(-1)) {
  ------------------
  |  Branch (1331:13): [True: 208k, False: 25.8k]
  ------------------
 1332|       |            // Nothing to improve in chunk_idx. Need to continue with other chunks, if any.
 1333|   208k|            return !m_suboptimal_chunks.empty();
 1334|   208k|        }
 1335|       |        // Deactivate the found dependency and then make the state topological again with a
 1336|       |        // sequence of merges.
 1337|  25.8k|        Improve(parent_idx, child_idx);
 1338|  25.8k|        return true;
 1339|   234k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE19PickChunkToOptimizeEv:
 1123|   234k|    {
 1124|   234k|        m_cost.PickChunkToOptimizeBegin();
 1125|   234k|        unsigned steps{0};
 1126|   235k|        while (!m_suboptimal_chunks.empty()) {
  ------------------
  |  Branch (1126:16): [True: 235k, False: 0]
  ------------------
 1127|   235k|            ++steps;
 1128|       |            // Pop an entry from the potentially-suboptimal chunk queue.
 1129|   235k|            SetIdx chunk_idx = m_suboptimal_chunks.front();
 1130|   235k|            Assume(m_suboptimal_idxs[chunk_idx]);
  ------------------
  |  |  128|   235k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1131|   235k|            m_suboptimal_idxs.Reset(chunk_idx);
 1132|   235k|            m_suboptimal_chunks.pop_front();
 1133|   235k|            if (m_chunk_idxs[chunk_idx]) {
  ------------------
  |  Branch (1133:17): [True: 234k, False: 703]
  ------------------
 1134|   234k|                m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
 1135|   234k|                return chunk_idx;
 1136|   234k|            }
 1137|       |            // If what was popped is not currently a chunk, continue. This may
 1138|       |            // happen when a split chunk merges in Improve() with one or more existing chunks that
 1139|       |            // are themselves on the suboptimal queue already.
 1140|   235k|        }
 1141|      0|        m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
 1142|      0|        return INVALID_SET_IDX;
 1143|   234k|    }
_ZN17cluster_linearize19SFLDefaultCostModel24PickChunkToOptimizeBeginEv:
  534|   310k|    inline void PickChunkToOptimizeBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel22PickChunkToOptimizeEndEi:
  535|   310k|    inline void PickChunkToOptimizeEnd(int num_steps) noexcept { m_cost += num_steps + 4; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE21PickDependencyToSplitEh:
 1147|   234k|    {
 1148|   234k|        m_cost.PickDependencyToSplitBegin();
 1149|   234k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|   234k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1150|   234k|        auto& chunk_info = m_set_info[chunk_idx];
 1151|       |
 1152|       |        // Remember the best dependency {par, chl} seen so far.
 1153|   234k|        std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)};
 1154|   234k|        uint64_t candidate_tiebreak = 0;
 1155|       |        // Iterate over all transactions.
 1156|   867k|        for (auto tx_idx : chunk_info.transactions) {
  ------------------
  |  Branch (1156:26): [True: 867k, False: 234k]
  ------------------
 1157|   867k|            const auto& tx_data = m_tx_data[tx_idx];
 1158|       |            // Iterate over all active child dependencies of the transaction.
 1159|   867k|            for (auto child_idx : tx_data.active_children) {
  ------------------
  |  Branch (1159:33): [True: 633k, False: 867k]
  ------------------
 1160|   633k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
 1161|       |                // Skip if this dependency is ineligible (the top chunk that would be created
 1162|       |                // does not have higher feerate than the chunk it is currently part of).
 1163|   633k|                auto cmp = ByRatio{dep_top_info.feerate} <=> ByRatio{chunk_info.feerate};
 1164|   633k|                if (cmp <= 0) continue;
  ------------------
  |  Branch (1164:21): [True: 563k, False: 69.3k]
  ------------------
 1165|       |                // Generate a random tiebreak for this dependency, and reject it if its tiebreak
 1166|       |                // is worse than the best so far. This means that among all eligible
 1167|       |                // dependencies, a uniformly random one will be chosen.
 1168|  69.3k|                uint64_t tiebreak = m_rng.rand64();
 1169|  69.3k|                if (tiebreak < candidate_tiebreak) continue;
  ------------------
  |  Branch (1169:21): [True: 29.1k, False: 40.2k]
  ------------------
 1170|       |                // Remember this as our (new) candidate dependency.
 1171|  40.2k|                candidate_dep = {tx_idx, child_idx};
 1172|  40.2k|                candidate_tiebreak = tiebreak;
 1173|  40.2k|            }
 1174|   867k|        }
 1175|   234k|        m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count());
 1176|   234k|        return candidate_dep;
 1177|   234k|    }
_ZN17cluster_linearize19SFLDefaultCostModel26PickDependencyToSplitBeginEv:
  536|   310k|    inline void PickDependencyToSplitBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel24PickDependencyToSplitEndEi:
  537|   310k|    inline void PickDependencyToSplitEnd(int num_txns) noexcept { m_cost += 8 * num_txns + 9; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE7ImproveEjj:
 1088|  25.8k|    {
 1089|       |        // Deactivate the specified dependency, splitting it into two new chunks: a top containing
 1090|       |        // the parent, and a bottom containing the child. The top should have a higher feerate.
 1091|  25.8k|        auto [parent_chunk_idx, child_chunk_idx] = Deactivate(parent_idx, child_idx);
 1092|       |
 1093|       |        // At this point we have exactly two chunks which may violate topology constraints (the
 1094|       |        // parent chunk and child chunk that were produced by deactivation). We can fix
 1095|       |        // these using just merge sequences, one upwards and one downwards, avoiding the need for a
 1096|       |        // full MakeTopological.
 1097|  25.8k|        const auto& parent_reachable = m_reachable[parent_chunk_idx].first;
 1098|  25.8k|        const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
 1099|  25.8k|        if (parent_reachable.Overlaps(child_chunk_txn)) {
  ------------------
  |  Branch (1099:13): [True: 5.16k, False: 20.6k]
  ------------------
 1100|       |            // The parent chunk has a dependency on a transaction in the child chunk. In this case,
 1101|       |            // the parent needs to merge back with the child chunk (a self-merge), and no other
 1102|       |            // merges are needed. Special-case this, so the overhead of PickMergeCandidate and
 1103|       |            // MergeSequence can be avoided.
 1104|       |
 1105|       |            // In the self-merge, the roles reverse: the parent chunk (from the split) depends
 1106|       |            // on the child chunk, so child_chunk_idx is the "top" and parent_chunk_idx is the
 1107|       |            // "bottom" for MergeChunks.
 1108|  5.16k|            auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
 1109|  5.16k|            if (!m_suboptimal_idxs[merged_chunk_idx]) {
  ------------------
  |  Branch (1109:17): [True: 5.16k, False: 0]
  ------------------
 1110|  5.16k|                m_suboptimal_idxs.Set(merged_chunk_idx);
 1111|  5.16k|                m_suboptimal_chunks.push_back(merged_chunk_idx);
 1112|  5.16k|            }
 1113|  20.6k|        } else {
 1114|       |            // Merge the top chunk with lower-feerate chunks it depends on.
 1115|  20.6k|            MergeSequence<false>(parent_chunk_idx);
 1116|       |            // Merge the bottom chunk with higher-feerate chunks that depend on it.
 1117|  20.6k|            MergeSequence<true>(child_chunk_idx);
 1118|  20.6k|        }
 1119|  25.8k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE10DeactivateEjj:
  892|   225k|    {
  893|   225k|        m_cost.DeactivateBegin();
  894|       |        // Gather and check information about the parent transactions.
  895|   225k|        auto& parent_data = m_tx_data[parent_idx];
  896|   225k|        Assume(parent_data.children[child_idx]);
  ------------------
  |  |  128|   225k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  897|   225k|        Assume(parent_data.active_children[child_idx]);
  ------------------
  |  |  128|   225k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  898|       |        // Get the top set of the active dependency (which will become the parent chunk) and the
  899|       |        // chunk set the transactions are currently in (which will become the bottom chunk).
  900|   225k|        auto parent_chunk_idx = parent_data.dep_top_idx[child_idx];
  901|   225k|        auto child_chunk_idx = parent_data.chunk_idx;
  902|   225k|        Assume(parent_chunk_idx != child_chunk_idx);
  ------------------
  |  |  128|   225k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  903|   225k|        Assume(m_chunk_idxs[child_chunk_idx]);
  ------------------
  |  |  128|   225k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  904|   225k|        Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk
  ------------------
  |  |  128|   225k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  905|   225k|        auto& top_info = m_set_info[parent_chunk_idx];
  906|   225k|        auto& bottom_info = m_set_info[child_chunk_idx];
  907|       |
  908|       |        // Remove the active dependency.
  909|   225k|        parent_data.active_children.Reset(child_idx);
  910|   225k|        m_chunk_idxs.Set(parent_chunk_idx);
  911|   225k|        auto ntx = bottom_info.transactions.Count();
  912|       |        // Subtract the top_info from the bottom_info, as it will become the child chunk.
  913|   225k|        bottom_info -= top_info;
  914|       |        // See the comment above in Activate(). We perform the opposite operations here, removing
  915|       |        // instead of adding. Simultaneously, aggregate the top/bottom's union of parents/children.
  916|   225k|        SetType top_parents, top_children;
  917|   610k|        for (auto tx_idx : top_info.transactions) {
  ------------------
  |  Branch (917:26): [True: 610k, False: 225k]
  ------------------
  918|   610k|            auto& tx_data = m_tx_data[tx_idx];
  919|   610k|            tx_data.chunk_idx = parent_chunk_idx;
  920|   610k|            top_parents |= tx_data.parents;
  921|   610k|            top_children |= tx_data.children;
  922|   610k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (922:37): [True: 385k, False: 610k]
  ------------------
  923|   385k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  924|   385k|                if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info;
  ------------------
  |  Branch (924:21): [True: 184k, False: 200k]
  ------------------
  925|   385k|            }
  926|   610k|        }
  927|   225k|        SetType bottom_parents, bottom_children;
  928|   495k|        for (auto tx_idx : bottom_info.transactions) {
  ------------------
  |  Branch (928:26): [True: 495k, False: 225k]
  ------------------
  929|   495k|            auto& tx_data = m_tx_data[tx_idx];
  930|   495k|            bottom_parents |= tx_data.parents;
  931|   495k|            bottom_children |= tx_data.children;
  932|   495k|            for (auto dep_child_idx : tx_data.active_children) {
  ------------------
  |  Branch (932:37): [True: 270k, False: 495k]
  ------------------
  933|   270k|                auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
  934|   270k|                if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info;
  ------------------
  |  Branch (934:21): [True: 142k, False: 127k]
  ------------------
  935|   270k|            }
  936|   495k|        }
  937|       |        // Compute the new sets of reachable transactions for each new chunk, based on the
  938|       |        // top/bottom parents and children computed above.
  939|   225k|        m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions;
  940|   225k|        m_reachable[parent_chunk_idx].second = top_children - top_info.transactions;
  941|   225k|        m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions;
  942|   225k|        m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions;
  943|       |        // Return the two new set idxs.
  944|   225k|        m_cost.DeactivateEnd(/*num_deps=*/ntx - 1);
  945|   225k|        return {parent_chunk_idx, child_chunk_idx};
  946|   225k|    }
_ZN17cluster_linearize19SFLDefaultCostModel15DeactivateBeginEv:
  527|   248k|    inline void DeactivateBegin() noexcept {}
_ZN17cluster_linearize7SetInfoIN13bitset_detail9IntBitSetImEEEmIERKS4_:
  402|   552k|    {
  403|   552k|        Assume(other.transactions.IsSubsetOf(transactions));
  ------------------
  |  |  128|   552k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  404|   552k|        transactions -= other.transactions;
  405|   552k|        feerate -= other.feerate;
  406|   552k|        return *this;
  407|   552k|    }
_ZN17cluster_linearize19SFLDefaultCostModel13DeactivateEndEi:
  528|   248k|    inline void DeactivateEnd(int num_deps) noexcept { m_cost += 11 * num_deps + 8; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE13MergeSequenceILb0EEEvh:
 1071|  20.6k|    {
 1072|  20.6k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|  20.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1073|  21.4k|        while (true) {
  ------------------
  |  Branch (1073:16): [True: 21.4k, Folded]
  ------------------
 1074|  21.4k|            auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx);
 1075|  21.4k|            if (merged_chunk_idx == INVALID_SET_IDX) break;
  ------------------
  |  Branch (1075:17): [True: 20.6k, False: 790]
  ------------------
 1076|    790|            chunk_idx = merged_chunk_idx;
 1077|    790|        }
 1078|       |        // Add the chunk to the queue of improvable chunks, if it wasn't already there.
 1079|  20.6k|        if (!m_suboptimal_idxs[chunk_idx]) {
  ------------------
  |  Branch (1079:13): [True: 20.6k, False: 0]
  ------------------
 1080|  20.6k|            m_suboptimal_idxs.Set(chunk_idx);
 1081|  20.6k|            m_suboptimal_chunks.push_back(chunk_idx);
 1082|  20.6k|        }
 1083|  20.6k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE13MergeSequenceILb1EEEvh:
 1071|  20.6k|    {
 1072|  20.6k|        Assume(m_chunk_idxs[chunk_idx]);
  ------------------
  |  |  128|  20.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1073|  29.6k|        while (true) {
  ------------------
  |  Branch (1073:16): [True: 29.6k, Folded]
  ------------------
 1074|  29.6k|            auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx);
 1075|  29.6k|            if (merged_chunk_idx == INVALID_SET_IDX) break;
  ------------------
  |  Branch (1075:17): [True: 20.6k, False: 8.98k]
  ------------------
 1076|  8.98k|            chunk_idx = merged_chunk_idx;
 1077|  8.98k|        }
 1078|       |        // Add the chunk to the queue of improvable chunks, if it wasn't already there.
 1079|  20.6k|        if (!m_suboptimal_idxs[chunk_idx]) {
  ------------------
  |  Branch (1079:13): [True: 16.8k, False: 3.79k]
  ------------------
 1080|  16.8k|            m_suboptimal_idxs.Set(chunk_idx);
 1081|  16.8k|            m_suboptimal_chunks.push_back(chunk_idx);
 1082|  16.8k|        }
 1083|  20.6k|    }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE15StartMinimizingEv:
 1344|  87.3k|    {
 1345|  87.3k|        m_cost.StartMinimizingBegin();
 1346|  87.3k|        m_nonminimal_chunks.clear();
 1347|  87.3k|        m_nonminimal_chunks.reserve(m_transaction_idxs.Count());
 1348|       |        // Gather all chunks, and for each, add it with a random pivot in it, and a random initial
 1349|       |        // direction, to m_nonminimal_chunks.
 1350|   193k|        for (auto chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1350:29): [True: 193k, False: 87.3k]
  ------------------
 1351|   193k|            TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions);
 1352|   193k|            m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>());
 1353|       |            // Randomize the initial order of nonminimal chunks in the queue.
 1354|   193k|            SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size());
 1355|   193k|            if (j != m_nonminimal_chunks.size() - 1) {
  ------------------
  |  Branch (1355:17): [True: 71.9k, False: 121k]
  ------------------
 1356|  71.9k|                std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]);
 1357|  71.9k|            }
 1358|   193k|        }
 1359|  87.3k|        m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size());
 1360|  87.3k|    }
_ZN17cluster_linearize19SFLDefaultCostModel20StartMinimizingBeginEv:
  538|  90.0k|    inline void StartMinimizingBegin() noexcept {}
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE12PickRandomTxERKS3_:
  791|   388k|    {
  792|   388k|        Assume(tx_idxs.Any());
  ------------------
  |  |  128|   388k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  793|   388k|        unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
  794|   635k|        for (auto tx_idx : tx_idxs) {
  ------------------
  |  Branch (794:26): [True: 635k, False: 0]
  ------------------
  795|   635k|            if (pos == 0) return tx_idx;
  ------------------
  |  Branch (795:17): [True: 388k, False: 247k]
  ------------------
  796|   247k|            --pos;
  797|   247k|        }
  798|      0|        Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  799|      0|        return TxIdx(-1);
  800|   388k|    }
_ZN17cluster_linearize19SFLDefaultCostModel18StartMinimizingEndEi:
  539|  90.0k|    inline void StartMinimizingEnd(int num_chunks) noexcept { m_cost += 18 * num_chunks; }
_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE12MinimizeStepEv:
 1364|   692k|    {
 1365|       |        // If the queue of potentially-non-minimal chunks is empty, we are done.
 1366|   692k|        if (m_nonminimal_chunks.empty()) return false;
  ------------------
  |  Branch (1366:13): [True: 75.6k, False: 616k]
  ------------------
 1367|   616k|        m_cost.MinimizeStepBegin();
 1368|       |        // Pop an entry from the potentially-non-minimal chunk queue.
 1369|   616k|        auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front();
 1370|   616k|        m_nonminimal_chunks.pop_front();
 1371|   616k|        auto& chunk_info = m_set_info[chunk_idx];
 1372|       |        /** Whether to move the pivot down rather than up. */
 1373|   616k|        bool move_pivot_down = flags & 1;
 1374|       |        /** Whether this is already the second stage. */
 1375|   616k|        bool second_stage = flags & 2;
 1376|       |
 1377|       |        // Find a random dependency whose top and bottom set feerates are equal, and which has
 1378|       |        // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
 1379|   616k|        std::pair<TxIdx, TxIdx> candidate_dep;
 1380|   616k|        uint64_t candidate_tiebreak{0};
 1381|   616k|        bool have_any = false;
 1382|       |        // Iterate over all transactions.
 1383|  1.52M|        for (auto tx_idx : chunk_info.transactions) {
  ------------------
  |  Branch (1383:26): [True: 1.52M, False: 616k]
  ------------------
 1384|  1.52M|            const auto& tx_data = m_tx_data[tx_idx];
 1385|       |            // Iterate over all active child dependencies of the transaction.
 1386|  1.52M|            for (auto child_idx : tx_data.active_children) {
  ------------------
  |  Branch (1386:33): [True: 908k, False: 1.52M]
  ------------------
 1387|   908k|                const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
 1388|       |                // Skip if this dependency does not have equal top and bottom set feerates. Note
 1389|       |                // that the top cannot have higher feerate than the bottom, or OptimizeSteps would
 1390|       |                // have dealt with it.
 1391|   908k|                if (ByRatio{dep_top_info.feerate} < ByRatio{chunk_info.feerate}) continue;
  ------------------
  |  Branch (1391:21): [True: 155k, False: 752k]
  ------------------
 1392|   752k|                have_any = true;
 1393|       |                // Skip if this dependency does not have pivot in the right place.
 1394|   752k|                if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue;
  ------------------
  |  Branch (1394:21): [True: 345k, False: 406k]
  ------------------
 1395|       |                // Remember this as our chosen dependency if it has a better tiebreak.
 1396|   406k|                uint64_t tiebreak = m_rng.rand64() | 1;
 1397|   406k|                if (tiebreak > candidate_tiebreak) {
  ------------------
  |  Branch (1397:21): [True: 272k, False: 134k]
  ------------------
 1398|   272k|                    candidate_tiebreak = tiebreak;
 1399|   272k|                    candidate_dep = {tx_idx, child_idx};
 1400|   272k|                }
 1401|   406k|            }
 1402|  1.52M|        }
 1403|   616k|        m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count());
 1404|       |        // If no dependencies have equal top and bottom set feerate, this chunk is minimal.
 1405|   616k|        if (!have_any) return true;
  ------------------
  |  Branch (1405:13): [True: 355k, False: 261k]
  ------------------
 1406|       |        // If all found dependencies have the pivot in the wrong place, try moving it in the other
 1407|       |        // direction. If this was the second stage already, we are done.
 1408|   261k|        if (candidate_tiebreak == 0) {
  ------------------
  |  Branch (1408:13): [True: 61.9k, False: 199k]
  ------------------
 1409|       |            // Switch to other direction, and to second phase.
 1410|  61.9k|            flags ^= 3;
 1411|  61.9k|            if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags);
  ------------------
  |  Branch (1411:17): [True: 61.7k, False: 223]
  ------------------
 1412|  61.9k|            return true;
 1413|  61.9k|        }
 1414|       |
 1415|       |        // Otherwise, deactivate the dependency that was found.
 1416|   199k|        auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second);
 1417|       |        // Determine if there is a dependency from the new bottom to the new top (opposite from the
 1418|       |        // dependency that was just deactivated).
 1419|   199k|        auto& parent_reachable = m_reachable[parent_chunk_idx].first;
 1420|   199k|        auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
 1421|   199k|        if (parent_reachable.Overlaps(child_chunk_txn)) {
  ------------------
  |  Branch (1421:13): [True: 5.38k, False: 194k]
  ------------------
 1422|       |            // A self-merge is needed. Note that the child_chunk_idx is the top, and
 1423|       |            // parent_chunk_idx is the bottom, because we activate a dependency in the reverse
 1424|       |            // direction compared to the deactivation above.
 1425|  5.38k|            auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
 1426|       |            // Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx
 1427|       |            // will have changed.
 1428|  5.38k|            m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags);
 1429|  5.38k|            m_cost.MinimizeStepEnd(/*split=*/false);
 1430|   194k|        } else {
 1431|       |            // No self-merge happens, and thus we have found a way to split the chunk. Create two
 1432|       |            // smaller chunks, and add them to the queue. The one that contains the current pivot
 1433|       |            // gets to continue with it in the same direction, to minimize the number of times we
 1434|       |            // alternate direction. If we were in the second phase already, the newly created chunk
 1435|       |            // inherits that too, because we know no split with the pivot on the other side is
 1436|       |            // possible already. The new chunk without the current pivot gets a new randomly-chosen
 1437|       |            // one.
 1438|   194k|            if (move_pivot_down) {
  ------------------
  |  Branch (1438:17): [True: 96.2k, False: 97.9k]
  ------------------
 1439|  96.2k|                auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions);
 1440|  96.2k|                m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>());
 1441|  96.2k|                m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags);
 1442|  97.9k|            } else {
 1443|  97.9k|                auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions);
 1444|  97.9k|                m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags);
 1445|  97.9k|                m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>());
 1446|  97.9k|            }
 1447|   194k|            if (m_rng.randbool()) {
  ------------------
  |  Branch (1447:17): [True: 99.3k, False: 94.8k]
  ------------------
 1448|  99.3k|                std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]);
 1449|  99.3k|            }
 1450|   194k|            m_cost.MinimizeStepEnd(/*split=*/true);
 1451|   194k|        }
 1452|   199k|        return true;
 1453|   261k|    }
_ZN17cluster_linearize19SFLDefaultCostModel17MinimizeStepBeginEv:
  540|   742k|    inline void MinimizeStepBegin() noexcept {}
_ZN17cluster_linearize19SFLDefaultCostModel15MinimizeStepMidEi:
  541|   742k|    inline void MinimizeStepMid(int num_txns) noexcept { m_cost += 11 * num_txns + 11; }
_ZN17cluster_linearize19SFLDefaultCostModel15MinimizeStepEndEb:
  542|   222k|    inline void MinimizeStepEnd(bool split) noexcept { m_cost += 17 * split + 7; }
txgraph.cpp:_ZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS8_11TxGraphImplEimE3$_0EENSt3__16vectorIjNSD_9allocatorIjEEEERKT_:
 1472|   224k|    {
 1473|   224k|        m_cost.GetLinearizationBegin();
 1474|       |        /** The output linearization. */
 1475|   224k|        std::vector<DepGraphIndex> ret;
 1476|   224k|        ret.reserve(m_set_info.size());
 1477|       |        /** A heap with all chunks (by set index) that can currently be included, sorted by
 1478|       |         *  chunk feerate (high to low), chunk size (small to large), and by least maximum element
 1479|       |         *  according to the fallback order (which is the second pair element). */
 1480|   224k|        std::array<std::pair<SetIdx, TxIdx>, SetType::Size()> ready_chunks;
 1481|       |        /** The number of entries of ready_chunks in use. */
 1482|   224k|        unsigned num_ready_chunks{0};
 1483|       |        /** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on
 1484|       |         *  other chunks (not including dependencies within the chunk itself). */
 1485|   224k|        std::array<TxIdx, SetType::Size()> chunk_deps;
 1486|   224k|        std::fill_n(chunk_deps.begin(), m_set_info.size(), TxIdx{0});
 1487|       |        /** For every transaction, indexed by TxIdx, the number of unmet dependencies the
 1488|       |         *  transaction has. */
 1489|   224k|        std::array<TxIdx, SetType::Size()> tx_deps;
 1490|   224k|        std::fill_n(tx_deps.begin(), m_tx_data.size(), TxIdx{0});
 1491|       |        /** A heap with all transactions within the current chunk that can be included, sorted by
 1492|       |         *  tx feerate (high to low), tx size (small to large), and fallback order. */
 1493|   224k|        std::array<TxIdx, SetType::Size()> ready_tx;
 1494|       |        /** The number of entries of ready_tx in use. */
 1495|   224k|        unsigned num_ready_tx{0};
 1496|       |        // Populate chunk_deps and tx_deps.
 1497|   224k|        unsigned num_deps{0};
 1498|  2.54M|        for (TxIdx chl_idx : m_transaction_idxs) {
  ------------------
  |  Branch (1498:28): [True: 2.54M, False: 224k]
  ------------------
 1499|  2.54M|            const auto& chl_data = m_tx_data[chl_idx];
 1500|  2.54M|            tx_deps[chl_idx] = chl_data.parents.Count();
 1501|  2.54M|            num_deps += tx_deps[chl_idx];
 1502|  2.54M|            auto chl_chunk_idx = chl_data.chunk_idx;
 1503|  2.54M|            auto& chl_chunk_info = m_set_info[chl_chunk_idx];
 1504|  2.54M|            chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count();
 1505|  2.54M|        }
 1506|       |        /** Function to compute the highest element of a chunk, by fallback_order. */
 1507|   224k|        auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept {
 1508|   224k|            auto& chunk = m_set_info[chunk_idx].transactions;
 1509|   224k|            auto it = chunk.begin();
 1510|   224k|            DepGraphIndex ret = *it;
 1511|   224k|            ++it;
 1512|   224k|            while (it != chunk.end()) {
 1513|   224k|                if (fallback_order(*it, ret) > 0) ret = *it;
 1514|   224k|                ++it;
 1515|   224k|            }
 1516|   224k|            return ret;
 1517|   224k|        };
 1518|       |        /** Comparison function for the transaction heap. Note that it is a max-heap, so
 1519|       |         *  tx_cmp_fn(a, b) == true means "a appears after b in the linearization". */
 1520|   224k|        auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1521|       |            // Bail out for identical transactions.
 1522|   224k|            if (a == b) return false;
 1523|       |            // First sort by increasing transaction feerate.
 1524|   224k|            auto& a_feerate = m_depgraph.FeeRate(a);
 1525|   224k|            auto& b_feerate = m_depgraph.FeeRate(b);
 1526|   224k|            auto feerate_cmp = ByRatio{a_feerate} <=> ByRatio{b_feerate};
 1527|   224k|            if (feerate_cmp != 0) return feerate_cmp < 0;
 1528|       |            // Then by decreasing transaction size.
 1529|   224k|            if (a_feerate.size != b_feerate.size) {
 1530|   224k|                return a_feerate.size > b_feerate.size;
 1531|   224k|            }
 1532|       |            // Tie-break by decreasing fallback_order.
 1533|   224k|            auto fallback_cmp = fallback_order(a, b);
 1534|   224k|            if (fallback_cmp != 0) return fallback_cmp > 0;
 1535|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1536|   224k|            Assume(false);
 1537|   224k|            return a < b;
 1538|   224k|        };
 1539|       |        // Construct a heap with all chunks that have no out-of-chunk dependencies.
 1540|       |        /** Comparison function for the chunk heap. Note that it is a max-heap, so
 1541|       |         *  chunk_cmp_fn(a, b) == true means "a appears after b in the linearization". */
 1542|   224k|        auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1543|       |            // Bail out for identical chunks.
 1544|   224k|            if (a.first == b.first) return false;
 1545|       |            // First sort by increasing chunk feerate.
 1546|   224k|            auto& chunk_feerate_a = m_set_info[a.first].feerate;
 1547|   224k|            auto& chunk_feerate_b = m_set_info[b.first].feerate;
 1548|   224k|            auto feerate_cmp = ByRatio{chunk_feerate_a} <=> ByRatio{chunk_feerate_b};
 1549|   224k|            if (feerate_cmp != 0) return feerate_cmp < 0;
 1550|       |            // Then by decreasing chunk size.
 1551|   224k|            if (chunk_feerate_a.size != chunk_feerate_b.size) {
 1552|   224k|                return chunk_feerate_a.size > chunk_feerate_b.size;
 1553|   224k|            }
 1554|       |            // Tie-break by decreasing fallback_order.
 1555|   224k|            auto fallback_cmp = fallback_order(a.second, b.second);
 1556|   224k|            if (fallback_cmp != 0) return fallback_cmp > 0;
 1557|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1558|   224k|            Assume(false);
 1559|   224k|            return a.second < b.second;
 1560|   224k|        };
 1561|       |        // Construct a heap with all chunks that have no out-of-chunk dependencies.
 1562|   901k|        for (SetIdx chunk_idx : m_chunk_idxs) {
  ------------------
  |  Branch (1562:31): [True: 901k, False: 224k]
  ------------------
 1563|   901k|            if (chunk_deps[chunk_idx] == 0) {
  ------------------
  |  Branch (1563:17): [True: 368k, False: 533k]
  ------------------
 1564|   368k|                ready_chunks[num_ready_chunks++] = {chunk_idx, max_fallback_fn(chunk_idx)};
 1565|   368k|            }
 1566|   901k|        }
 1567|   224k|        std::make_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1568|       |        // Pop chunks off the heap.
 1569|  1.12M|        while (num_ready_chunks > 0) {
  ------------------
  |  Branch (1569:16): [True: 901k, False: 224k]
  ------------------
 1570|   901k|            auto [chunk_idx, _rnd] = ready_chunks.front();
 1571|   901k|            std::pop_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1572|   901k|            --num_ready_chunks;
 1573|   901k|            Assume(chunk_deps[chunk_idx] == 0);
  ------------------
  |  |  128|   901k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1574|   901k|            const auto& chunk_txn = m_set_info[chunk_idx].transactions;
 1575|       |            // Build heap of all includable transactions in chunk.
 1576|   901k|            Assume(num_ready_tx == 0);
  ------------------
  |  |  128|   901k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1577|  2.54M|            for (TxIdx tx_idx : chunk_txn) {
  ------------------
  |  Branch (1577:31): [True: 2.54M, False: 901k]
  ------------------
 1578|  2.54M|                if (tx_deps[tx_idx] == 0) ready_tx[num_ready_tx++] = tx_idx;
  ------------------
  |  Branch (1578:21): [True: 1.18M, False: 1.35M]
  ------------------
 1579|  2.54M|            }
 1580|   901k|            Assume(num_ready_tx > 0);
  ------------------
  |  |  128|   901k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1581|   901k|            std::make_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1582|       |            // Pick transactions from the ready heap, append them to linearization, and decrement
 1583|       |            // dependency counts.
 1584|  3.44M|            while (num_ready_tx > 0) {
  ------------------
  |  Branch (1584:20): [True: 2.54M, False: 901k]
  ------------------
 1585|       |                // Pop an element from the tx_ready heap.
 1586|  2.54M|                auto tx_idx = ready_tx.front();
 1587|  2.54M|                std::pop_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1588|  2.54M|                --num_ready_tx;
 1589|       |                // Append to linearization.
 1590|  2.54M|                ret.push_back(tx_idx);
 1591|       |                // Decrement dependency counts.
 1592|  2.54M|                auto& tx_data = m_tx_data[tx_idx];
 1593|  2.61M|                for (TxIdx chl_idx : tx_data.children) {
  ------------------
  |  Branch (1593:36): [True: 2.61M, False: 2.54M]
  ------------------
 1594|  2.61M|                    auto& chl_data = m_tx_data[chl_idx];
 1595|       |                    // Decrement tx dependency count.
 1596|  2.61M|                    Assume(tx_deps[chl_idx] > 0);
  ------------------
  |  |  128|  2.61M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1597|  2.61M|                    if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) {
  ------------------
  |  Branch (1597:25): [True: 1.85M, False: 767k]
  |  Branch (1597:52): [True: 1.35M, False: 498k]
  ------------------
 1598|       |                        // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap.
 1599|  1.35M|                        ready_tx[num_ready_tx++] = chl_idx;
 1600|  1.35M|                        std::push_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
 1601|  1.35M|                    }
 1602|       |                    // Decrement chunk dependency count if this is out-of-chunk dependency.
 1603|  2.61M|                    if (chl_data.chunk_idx != chunk_idx) {
  ------------------
  |  Branch (1603:25): [True: 867k, False: 1.75M]
  ------------------
 1604|   867k|                        Assume(chunk_deps[chl_data.chunk_idx] > 0);
  ------------------
  |  |  128|   867k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1605|   867k|                        if (--chunk_deps[chl_data.chunk_idx] == 0) {
  ------------------
  |  Branch (1605:29): [True: 533k, False: 333k]
  ------------------
 1606|       |                            // Child chunk has no dependencies left. Add it to the chunk heap.
 1607|   533k|                            ready_chunks[num_ready_chunks++] = {chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)};
 1608|   533k|                            std::push_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
 1609|   533k|                        }
 1610|   867k|                    }
 1611|  2.61M|                }
 1612|  2.54M|            }
 1613|   901k|        }
 1614|   224k|        Assume(ret.size() == m_set_info.size());
  ------------------
  |  |  128|   224k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1615|   224k|        m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps);
 1616|   224k|        return ret;
 1617|   224k|    }
_ZN17cluster_linearize19SFLDefaultCostModel21GetLinearizationBeginEv:
  510|   227k|    inline void GetLinearizationBegin() noexcept {}
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS8_11TxGraphImplEimE3$_0EENSt3__16vectorIjNSD_9allocatorIjEEEERKT_ENKUlhE_clEh:
 1507|   901k|        auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept {
 1508|   901k|            auto& chunk = m_set_info[chunk_idx].transactions;
 1509|   901k|            auto it = chunk.begin();
 1510|   901k|            DepGraphIndex ret = *it;
 1511|   901k|            ++it;
 1512|  2.54M|            while (it != chunk.end()) {
  ------------------
  |  Branch (1512:20): [True: 1.63M, False: 901k]
  ------------------
 1513|  1.63M|                if (fallback_order(*it, ret) > 0) ret = *it;
  ------------------
  |  Branch (1513:21): [True: 410k, False: 1.22M]
  ------------------
 1514|  1.63M|                ++it;
 1515|  1.63M|            }
 1516|   901k|            return ret;
 1517|   901k|        };
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS8_11TxGraphImplEimE3$_0EENSt3__16vectorIjNSD_9allocatorIjEEEERKT_ENKUlSK_RKT0_E0_clINSD_4pairIhjEESR_EEDaSK_SN_:
 1542|  1.86M|        auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1543|       |            // Bail out for identical chunks.
 1544|  1.86M|            if (a.first == b.first) return false;
  ------------------
  |  Branch (1544:17): [True: 0, False: 1.86M]
  ------------------
 1545|       |            // First sort by increasing chunk feerate.
 1546|  1.86M|            auto& chunk_feerate_a = m_set_info[a.first].feerate;
 1547|  1.86M|            auto& chunk_feerate_b = m_set_info[b.first].feerate;
 1548|  1.86M|            auto feerate_cmp = ByRatio{chunk_feerate_a} <=> ByRatio{chunk_feerate_b};
 1549|  1.86M|            if (feerate_cmp != 0) return feerate_cmp < 0;
  ------------------
  |  Branch (1549:17): [True: 849k, False: 1.01M]
  ------------------
 1550|       |            // Then by decreasing chunk size.
 1551|  1.01M|            if (chunk_feerate_a.size != chunk_feerate_b.size) {
  ------------------
  |  Branch (1551:17): [True: 392k, False: 622k]
  ------------------
 1552|   392k|                return chunk_feerate_a.size > chunk_feerate_b.size;
 1553|   392k|            }
 1554|       |            // Tie-break by decreasing fallback_order.
 1555|   622k|            auto fallback_cmp = fallback_order(a.second, b.second);
 1556|   622k|            if (fallback_cmp != 0) return fallback_cmp > 0;
  ------------------
  |  Branch (1556:17): [True: 622k, False: 0]
  ------------------
 1557|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1558|      0|            Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1559|      0|            return a.second < b.second;
 1560|   622k|        };
txgraph.cpp:_ZZN17cluster_linearize19SpanningForestStateIN13bitset_detail9IntBitSetImEENS_19SFLDefaultCostModelEE16GetLinearizationITkNS_16StrongComparatorIjEEZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS8_11TxGraphImplEimE3$_0EENSt3__16vectorIjNSD_9allocatorIjEEEERKT_ENKUlSK_RKT0_E_clIjjEEDaSK_SN_:
 1520|  3.57M|        auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
 1521|       |            // Bail out for identical transactions.
 1522|  3.57M|            if (a == b) return false;
  ------------------
  |  Branch (1522:17): [True: 0, False: 3.57M]
  ------------------
 1523|       |            // First sort by increasing transaction feerate.
 1524|  3.57M|            auto& a_feerate = m_depgraph.FeeRate(a);
 1525|  3.57M|            auto& b_feerate = m_depgraph.FeeRate(b);
 1526|  3.57M|            auto feerate_cmp = ByRatio{a_feerate} <=> ByRatio{b_feerate};
 1527|  3.57M|            if (feerate_cmp != 0) return feerate_cmp < 0;
  ------------------
  |  Branch (1527:17): [True: 643k, False: 2.93M]
  ------------------
 1528|       |            // Then by decreasing transaction size.
 1529|  2.93M|            if (a_feerate.size != b_feerate.size) {
  ------------------
  |  Branch (1529:17): [True: 731k, False: 2.20M]
  ------------------
 1530|   731k|                return a_feerate.size > b_feerate.size;
 1531|   731k|            }
 1532|       |            // Tie-break by decreasing fallback_order.
 1533|  2.20M|            auto fallback_cmp = fallback_order(a, b);
 1534|  2.20M|            if (fallback_cmp != 0) return fallback_cmp > 0;
  ------------------
  |  Branch (1534:17): [True: 2.20M, False: 0]
  ------------------
 1535|       |            // This should not be hit, because fallback_order defines a strong ordering.
 1536|      0|            Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1537|      0|            return a < b;
 1538|  2.20M|        };
_ZN17cluster_linearize19SFLDefaultCostModel19GetLinearizationEndEii:
  512|   227k|    {
  513|       |        // Note that we account for the cost of the final linearization at the beginning (see
  514|       |        // InitializeEnd), because the cost budget decision needs to be made before calling
  515|       |        // GetLinearization.
  516|       |        // This function exists here to allow overriding it easily for benchmark purposes.
  517|   227k|    }
_ZN17cluster_linearize13PostLinearizeIN13bitset_detail9IntBitSetImEEEEvRKNS_8DepGraphIT_EENSt3__14spanIjLm18446744073709551615EEE:
 1873|   224k|{
 1874|       |    // This algorithm performs a number of passes (currently 2); the even ones operate from back to
 1875|       |    // front, the odd ones from front to back. Each results in an equal-or-better linearization
 1876|       |    // than the one started from.
 1877|       |    // - One pass in either direction guarantees that the resulting chunks are connected.
 1878|       |    // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
 1879|       |    //   guarantee this for graphs where each transaction has at most one child; backward passes
 1880|       |    //   guarantee this for graphs where each transaction has at most one parent).
 1881|       |    // - Starting with a backward pass guarantees the moved-tree property.
 1882|       |    //
 1883|       |    // During an odd (forward) pass, the high-level operation is:
 1884|       |    // - Start with an empty list of groups L=[].
 1885|       |    // - For every transaction i in the old linearization, from front to back:
 1886|       |    //   - Append a new group C=[i], containing just i, to the back of L.
 1887|       |    //   - While L has at least one group before C, and the group immediately before C has feerate
 1888|       |    //     lower than C:
 1889|       |    //     - If C depends on P:
 1890|       |    //       - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
 1891|       |    //     - Otherwise:
 1892|       |    //       - Swap P with C, continuing with the now-moved C.
 1893|       |    // - The output linearization is the concatenation of the groups in L.
 1894|       |    //
 1895|       |    // During even (backward) passes, i iterates from the back to the front of the existing
 1896|       |    // linearization, and new groups are prepended instead of appended to the list L. To enable
 1897|       |    // more code reuse, both passes append groups, but during even passes the meanings of
 1898|       |    // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
 1899|       |    // on output.
 1900|       |    //
 1901|       |    // In the implementation below, the groups are represented by singly-linked lists (pointing
 1902|       |    // from the back to the front), which are themselves organized in a singly-linked circular
 1903|       |    // list (each group pointing to its predecessor, with a special sentinel group at the front
 1904|       |    // that points back to the last group).
 1905|       |    //
 1906|       |    // Information about transaction t is stored in entries[t + 1], while the sentinel is in
 1907|       |    // entries[0].
 1908|       |
 1909|       |    /** Index of the sentinel in the entries array below. */
 1910|   224k|    static constexpr DepGraphIndex SENTINEL{0};
 1911|       |    /** Indicator that a group has no previous transaction. */
 1912|   224k|    static constexpr DepGraphIndex NO_PREV_TX{0};
 1913|       |
 1914|       |
 1915|       |    /** Data structure per transaction entry. */
 1916|   224k|    struct TxEntry
 1917|   224k|    {
 1918|       |        /** The index of the previous transaction in this group; NO_PREV_TX if this is the first
 1919|       |         *  entry of a group. */
 1920|   224k|        DepGraphIndex prev_tx;
 1921|       |
 1922|       |        // The fields below are only used for transactions that are the last one in a group
 1923|       |        // (referred to as tail transactions below).
 1924|       |
 1925|       |        /** Index of the first transaction in this group, possibly itself. */
 1926|   224k|        DepGraphIndex first_tx;
 1927|       |        /** Index of the last transaction in the previous group. The first group (the sentinel)
 1928|       |         *  points back to the last group here, making it a singly-linked circular list. */
 1929|   224k|        DepGraphIndex prev_group;
 1930|       |        /** All transactions in the group. Empty for the sentinel. */
 1931|   224k|        SetType group;
 1932|       |        /** All dependencies of the group (descendants in even passes; ancestors in odd ones). */
 1933|   224k|        SetType deps;
 1934|       |        /** The combined fee/size of transactions in the group. Fee is negated in even passes. */
 1935|   224k|        FeeFrac feerate;
 1936|   224k|    };
 1937|       |
 1938|       |    // As an example, consider the state corresponding to the linearization [1,0,3,2], with
 1939|       |    // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
 1940|       |    //
 1941|       |    //                                        +-----+
 1942|       |    //                                 0<-P-- | 0 S | ---\     Legend:
 1943|       |    //                                        +-----+    |
 1944|       |    //                                           ^       |     - digit in box: entries index
 1945|       |    //             /--------------F---------+    G       |       (note: one more than tx value)
 1946|       |    //             v                         \   |       |     - S: sentinel group
 1947|       |    //          +-----+        +-----+        +-----+    |          (empty feerate)
 1948|       |    //   0<-P-- | 2   | <--P-- | 1   | <--P-- | 4 T |    |     - T: tail transaction, contains
 1949|       |    //          +-----+        +-----+        +-----+    |          fields beyond prev_tv.
 1950|       |    //                                           ^       |     - P: prev_tx reference
 1951|       |    //                                           G       G     - F: first_tx reference
 1952|       |    //                                           |       |     - G: prev_group reference
 1953|       |    //                                        +-----+    |
 1954|       |    //                                 0<-P-- | 3 T | <--/
 1955|       |    //                                        +-----+
 1956|       |    //                                         ^   |
 1957|       |    //                                         \-F-/
 1958|       |    //
 1959|       |    // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
 1960|       |    // groups [2] and [3,0,1].
 1961|       |
 1962|   224k|    std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
 1963|       |
 1964|       |    // Perform two passes over the linearization.
 1965|   673k|    for (int pass = 0; pass < 2; ++pass) {
  ------------------
  |  Branch (1965:24): [True: 449k, False: 224k]
  ------------------
 1966|   449k|        int rev = !(pass & 1);
 1967|       |        // Construct a sentinel group, identifying the start of the list.
 1968|   449k|        entries[SENTINEL].prev_group = SENTINEL;
 1969|   449k|        Assume(entries[SENTINEL].feerate.IsEmpty());
  ------------------
  |  |  128|   449k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1970|       |
 1971|       |        // Iterate over all elements in the existing linearization.
 1972|  5.53M|        for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
  ------------------
  |  Branch (1972:35): [True: 5.08M, False: 449k]
  ------------------
 1973|       |            // Even passes are from back to front; odd passes from front to back.
 1974|  5.08M|            DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
  ------------------
  |  Branch (1974:47): [True: 2.54M, False: 2.54M]
  ------------------
 1975|       |            // Construct a new group containing just idx. In even passes, the meaning of
 1976|       |            // parent/child and high/low feerate are swapped.
 1977|  5.08M|            DepGraphIndex cur_group = idx + 1;
 1978|  5.08M|            entries[cur_group].group = SetType::Singleton(idx);
 1979|  5.08M|            entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
  ------------------
  |  Branch (1979:39): [True: 2.54M, False: 2.54M]
  ------------------
 1980|  5.08M|            entries[cur_group].feerate = depgraph.FeeRate(idx);
 1981|  5.08M|            if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
  ------------------
  |  Branch (1981:17): [True: 2.54M, False: 2.54M]
  ------------------
 1982|  5.08M|            entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
 1983|  5.08M|            entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
 1984|       |            // Insert the new group at the back of the groups linked list.
 1985|  5.08M|            entries[cur_group].prev_group = entries[SENTINEL].prev_group;
 1986|  5.08M|            entries[SENTINEL].prev_group = cur_group;
 1987|       |
 1988|       |            // Start merge/swap cycle.
 1989|  5.08M|            DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
 1990|  5.08M|            DepGraphIndex prev_group = entries[cur_group].prev_group;
 1991|       |            // Continue as long as the current group has higher feerate than the previous one.
 1992|  7.65M|            while (ByRatio{entries[cur_group].feerate} > ByRatio{entries[prev_group].feerate}) {
  ------------------
  |  Branch (1992:20): [True: 2.56M, False: 5.08M]
  ------------------
 1993|       |                // prev_group/cur_group/next_group refer to (the last transactions of) 3
 1994|       |                // consecutive entries in groups list.
 1995|  2.56M|                Assume(cur_group == entries[next_group].prev_group);
  ------------------
  |  |  128|  2.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1996|  2.56M|                Assume(prev_group == entries[cur_group].prev_group);
  ------------------
  |  |  128|  2.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1997|       |                // The sentinel has empty feerate, which is neither higher or lower than other
 1998|       |                // feerates. Thus, the while loop we are in here guarantees that cur_group and
 1999|       |                // prev_group are not the sentinel.
 2000|  2.56M|                Assume(cur_group != SENTINEL);
  ------------------
  |  |  128|  2.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2001|  2.56M|                Assume(prev_group != SENTINEL);
  ------------------
  |  |  128|  2.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2002|  2.56M|                if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
  ------------------
  |  Branch (2002:21): [True: 1.95M, False: 613k]
  ------------------
 2003|       |                    // There is a dependency between cur_group and prev_group; merge prev_group
 2004|       |                    // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
 2005|       |                    // but become unused.
 2006|  1.95M|                    entries[cur_group].group |= entries[prev_group].group;
 2007|  1.95M|                    entries[cur_group].deps |= entries[prev_group].deps;
 2008|  1.95M|                    entries[cur_group].feerate += entries[prev_group].feerate;
 2009|       |                    // Make the first of the current group point to the tail of the previous group.
 2010|  1.95M|                    entries[entries[cur_group].first_tx].prev_tx = prev_group;
 2011|       |                    // The first of the previous group becomes the first of the newly-merged group.
 2012|  1.95M|                    entries[cur_group].first_tx = entries[prev_group].first_tx;
 2013|       |                    // The previous group becomes whatever group was before the former one.
 2014|  1.95M|                    prev_group = entries[prev_group].prev_group;
 2015|  1.95M|                    entries[cur_group].prev_group = prev_group;
 2016|  1.95M|                } else {
 2017|       |                    // There is no dependency between cur_group and prev_group; swap them.
 2018|   613k|                    DepGraphIndex preprev_group = entries[prev_group].prev_group;
 2019|       |                    // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
 2020|       |                    // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
 2021|   613k|                    entries[next_group].prev_group = prev_group;
 2022|   613k|                    entries[prev_group].prev_group = cur_group;
 2023|   613k|                    entries[cur_group].prev_group = preprev_group;
 2024|       |                    // The current group remains the same, but the groups before/after it have
 2025|       |                    // changed.
 2026|   613k|                    next_group = prev_group;
 2027|   613k|                    prev_group = preprev_group;
 2028|   613k|                }
 2029|  2.56M|            }
 2030|  5.08M|        }
 2031|       |
 2032|       |        // Convert the entries back to linearization (overwriting the existing one).
 2033|   449k|        DepGraphIndex cur_group = entries[0].prev_group;
 2034|   449k|        DepGraphIndex done = 0;
 2035|  3.57M|        while (cur_group != SENTINEL) {
  ------------------
  |  Branch (2035:16): [True: 3.12M, False: 449k]
  ------------------
 2036|  3.12M|            DepGraphIndex cur_tx = cur_group;
 2037|       |            // Traverse the transactions of cur_group (from back to front), and write them in the
 2038|       |            // same order during odd passes, and reversed (front to back) in even passes.
 2039|  3.12M|            if (rev) {
  ------------------
  |  Branch (2039:17): [True: 1.56M, False: 1.56M]
  ------------------
 2040|  2.54M|                do {
 2041|  2.54M|                    *(linearization.begin() + (done++)) = cur_tx - 1;
 2042|  2.54M|                    cur_tx = entries[cur_tx].prev_tx;
 2043|  2.54M|                } while (cur_tx != NO_PREV_TX);
  ------------------
  |  Branch (2043:26): [True: 978k, False: 1.56M]
  ------------------
 2044|  1.56M|            } else {
 2045|  2.54M|                do {
 2046|  2.54M|                    *(linearization.end() - (++done)) = cur_tx - 1;
 2047|  2.54M|                    cur_tx = entries[cur_tx].prev_tx;
 2048|  2.54M|                } while (cur_tx != NO_PREV_TX);
  ------------------
  |  Branch (2048:26): [True: 976k, False: 1.56M]
  ------------------
 2049|  1.56M|            }
 2050|  3.12M|            cur_group = entries[cur_group].prev_group;
 2051|  3.12M|        }
 2052|   449k|        Assume(done == linearization.size());
  ------------------
  |  |  128|   449k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2053|   449k|    }
 2054|   224k|}
_ZN17cluster_linearize18ChunkLinearizationIN13bitset_detail9IntBitSetImEEEENSt3__16vectorI7FeeFracNS4_9allocatorIS6_EEEERKNS_8DepGraphIT_EENS4_4spanIKjLm18446744073709551615EEE:
  450|   115k|{
  451|   115k|    std::vector<FeeFrac> ret;
  452|  1.19M|    for (DepGraphIndex i : linearization) {
  ------------------
  |  Branch (452:26): [True: 1.19M, False: 115k]
  ------------------
  453|       |        /** The new chunk to be added, initially a singleton. */
  454|  1.19M|        auto new_chunk = depgraph.FeeRate(i);
  455|       |        // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
  456|  1.62M|        while (!ret.empty() && ByRatio{new_chunk} > ByRatio{ret.back()}) {
  ------------------
  |  Branch (456:16): [True: 1.46M, False: 162k]
  |  Branch (456:16): [True: 430k, False: 1.19M]
  |  Branch (456:32): [True: 430k, False: 1.03M]
  ------------------
  457|   430k|            new_chunk += ret.back();
  458|   430k|            ret.pop_back();
  459|   430k|        }
  460|       |        // Actually move that new chunk into the chunking.
  461|  1.19M|        ret.push_back(std::move(new_chunk));
  462|  1.19M|    }
  463|   115k|    return ret;
  464|   115k|}
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE7TxCountEv:
  121|   102k|    auto TxCount() const noexcept { return m_used.Count(); }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE9IsAcyclicEv:
  330|  51.5k|    {
  331|   602k|        for (auto i : Positions()) {
  ------------------
  |  Branch (331:21): [True: 602k, False: 51.5k]
  ------------------
  332|   602k|            if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
  ------------------
  |  Branch (332:17): [True: 0, False: 602k]
  ------------------
  333|      0|                return false;
  334|      0|            }
  335|   602k|        }
  336|  51.5k|        return true;
  337|  51.5k|    }
_ZNK17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE11IsConnectedERKS3_:
  302|   372k|    {
  303|   372k|        return FindConnectedComponent(subset) == subset;
  304|   372k|    }

_ZN11ArgsManagerD2Ev:
  130|      2|ArgsManager::~ArgsManager() = default;

_Z17internal_bswap_64m:
   64|   304k|{
   65|   304k|#ifdef bitcoin_builtin_bswap64
   66|   304k|    return bitcoin_builtin_bswap64(x);
  ------------------
  |  |   27|   304k|#      define bitcoin_builtin_bswap64(x) __builtin_bswap64(x)
  ------------------
   67|       |#else
   68|       |     return (((x & 0xff00000000000000ull) >> 56)
   69|       |          | ((x & 0x00ff000000000000ull) >> 40)
   70|       |          | ((x & 0x0000ff0000000000ull) >> 24)
   71|       |          | ((x & 0x000000ff00000000ull) >> 8)
   72|       |          | ((x & 0x00000000ff000000ull) << 8)
   73|       |          | ((x & 0x0000000000ff0000ull) << 24)
   74|       |          | ((x & 0x000000000000ff00ull) << 40)
   75|       |          | ((x & 0x00000000000000ffull) << 56));
   76|       |#endif
   77|   304k|}

_Z16be64toh_internalm:
   64|   194k|{
   65|   194k|    if constexpr (std::endian::native == std::endian::little) return internal_bswap_64(big_endian_64bits);
   66|       |        else return big_endian_64bits;
   67|   194k|}
_Z16htobe64_internalm:
   54|   109k|{
   55|   109k|    if constexpr (std::endian::native == std::endian::little) return internal_bswap_64(host_64bits);
   56|       |        else return host_64bits;
   57|   109k|}
_Z16le64toh_internalm:
   69|   226k|{
   70|       |    if constexpr (std::endian::native == std::endian::big) return internal_bswap_64(little_endian_64bits);
   71|   226k|        else return little_endian_64bits;
   72|   226k|}
_Z16htole32_internalj:
   39|   728k|{
   40|       |    if constexpr (std::endian::native == std::endian::big) return internal_bswap_32(host_32bits);
   41|   728k|        else return host_32bits;
   42|   728k|}
_Z16le32toh_internalj:
   49|   406k|{
   50|       |    if constexpr (std::endian::native == std::endian::big) return internal_bswap_32(little_endian_32bits);
   51|   406k|        else return little_endian_32bits;
   52|   406k|}

_ZN15ChaCha20Aligned6SetKeyENSt3__14spanIKSt4byteLm18446744073709551615EEE:
   25|  50.8k|{
   26|  50.8k|    assert(key.size() == KEYLEN);
  ------------------
  |  Branch (26:5): [True: 50.8k, False: 0]
  ------------------
   27|  50.8k|    input[0] = ReadLE32(key.data() + 0);
   28|  50.8k|    input[1] = ReadLE32(key.data() + 4);
   29|  50.8k|    input[2] = ReadLE32(key.data() + 8);
   30|  50.8k|    input[3] = ReadLE32(key.data() + 12);
   31|  50.8k|    input[4] = ReadLE32(key.data() + 16);
   32|  50.8k|    input[5] = ReadLE32(key.data() + 20);
   33|  50.8k|    input[6] = ReadLE32(key.data() + 24);
   34|  50.8k|    input[7] = ReadLE32(key.data() + 28);
   35|  50.8k|    input[8] = 0;
   36|  50.8k|    input[9] = 0;
   37|  50.8k|    input[10] = 0;
   38|  50.8k|    input[11] = 0;
   39|  50.8k|}
_ZN15ChaCha20AlignedD2Ev:
   42|  38.7k|{
   43|  38.7k|    memory_cleanse(input, sizeof(input));
   44|  38.7k|}
_ZN15ChaCha20AlignedC2ENSt3__14spanIKSt4byteLm18446744073709551615EEE:
   47|  38.7k|{
   48|  38.7k|    SetKey(key);
   49|  38.7k|}
_ZN8ChaCha209KeystreamENSt3__14spanISt4byteLm18446744073709551615EEE:
  282|   238k|{
  283|   238k|    if (out.empty()) return;
  ------------------
  |  Branch (283:9): [True: 0, False: 238k]
  ------------------
  284|   238k|    if (m_bufleft) {
  ------------------
  |  Branch (284:9): [True: 192k, False: 45.5k]
  ------------------
  285|   192k|        unsigned reuse = std::min<size_t>(m_bufleft, out.size());
  286|   192k|        std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
  287|   192k|        m_bufleft -= reuse;
  288|   192k|        out = out.subspan(reuse);
  289|   192k|    }
  290|   238k|    if (out.size() >= m_aligned.BLOCKLEN) {
  ------------------
  |  Branch (290:9): [True: 0, False: 238k]
  ------------------
  291|      0|        size_t blocks = out.size() / m_aligned.BLOCKLEN;
  292|      0|        m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
  293|      0|        out = out.subspan(blocks * m_aligned.BLOCKLEN);
  294|      0|    }
  295|   238k|    if (!out.empty()) {
  ------------------
  |  Branch (295:9): [True: 45.5k, False: 192k]
  ------------------
  296|  45.5k|        m_aligned.Keystream(m_buffer);
  297|  45.5k|        std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
  298|  45.5k|        m_bufleft = m_aligned.BLOCKLEN - out.size();
  299|  45.5k|    }
  300|   238k|}
_ZN8ChaCha20D2Ev:
  332|  38.7k|{
  333|  38.7k|    memory_cleanse(m_buffer.data(), m_buffer.size());
  334|  38.7k|}
_ZN8ChaCha206SetKeyENSt3__14spanIKSt4byteLm18446744073709551615EEE:
  337|  12.1k|{
  338|  12.1k|    m_aligned.SetKey(key);
  339|  12.1k|    m_bufleft = 0;
  340|  12.1k|    memory_cleanse(m_buffer.data(), m_buffer.size());
  341|  12.1k|}
_ZN15ChaCha20Aligned9KeystreamENSt3__14spanISt4byteLm18446744073709551615EEE:
   60|  45.5k|{
   61|  45.5k|    std::byte* c = output.data();
   62|  45.5k|    size_t blocks = output.size() / BLOCKLEN;
   63|  45.5k|    assert(blocks * BLOCKLEN == output.size());
  ------------------
  |  Branch (63:5): [True: 45.5k, False: 0]
  ------------------
   64|       |
   65|  45.5k|    uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
   66|  45.5k|    uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
   67|       |
   68|  45.5k|    if (!blocks) return;
  ------------------
  |  Branch (68:9): [True: 0, False: 45.5k]
  ------------------
   69|       |
   70|  45.5k|    j4 = input[0];
   71|  45.5k|    j5 = input[1];
   72|  45.5k|    j6 = input[2];
   73|  45.5k|    j7 = input[3];
   74|  45.5k|    j8 = input[4];
   75|  45.5k|    j9 = input[5];
   76|  45.5k|    j10 = input[6];
   77|  45.5k|    j11 = input[7];
   78|  45.5k|    j12 = input[8];
   79|  45.5k|    j13 = input[9];
   80|  45.5k|    j14 = input[10];
   81|  45.5k|    j15 = input[11];
   82|       |
   83|  45.5k|    for (;;) {
   84|  45.5k|        x0 = 0x61707865;
   85|  45.5k|        x1 = 0x3320646e;
   86|  45.5k|        x2 = 0x79622d32;
   87|  45.5k|        x3 = 0x6b206574;
   88|  45.5k|        x4 = j4;
   89|  45.5k|        x5 = j5;
   90|  45.5k|        x6 = j6;
   91|  45.5k|        x7 = j7;
   92|  45.5k|        x8 = j8;
   93|  45.5k|        x9 = j9;
   94|  45.5k|        x10 = j10;
   95|  45.5k|        x11 = j11;
   96|  45.5k|        x12 = j12;
   97|  45.5k|        x13 = j13;
   98|  45.5k|        x14 = j14;
   99|  45.5k|        x15 = j15;
  100|       |
  101|       |        // The 20 inner ChaCha20 rounds are unrolled here for performance.
  102|  45.5k|        REPEAT10(
  ------------------
  |  |   22|  45.5k|#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
  |  |  ------------------
  |  |  |  Branch (22:84): [Folded, False: 45.5k]
  |  |  ------------------
  ------------------
  103|  45.5k|            QUARTERROUND( x0, x4, x8,x12);
  104|  45.5k|            QUARTERROUND( x1, x5, x9,x13);
  105|  45.5k|            QUARTERROUND( x2, x6,x10,x14);
  106|  45.5k|            QUARTERROUND( x3, x7,x11,x15);
  107|  45.5k|            QUARTERROUND( x0, x5,x10,x15);
  108|  45.5k|            QUARTERROUND( x1, x6,x11,x12);
  109|  45.5k|            QUARTERROUND( x2, x7, x8,x13);
  110|  45.5k|            QUARTERROUND( x3, x4, x9,x14);
  111|  45.5k|        );
  112|       |
  113|  45.5k|        x0 += 0x61707865;
  114|  45.5k|        x1 += 0x3320646e;
  115|  45.5k|        x2 += 0x79622d32;
  116|  45.5k|        x3 += 0x6b206574;
  117|  45.5k|        x4 += j4;
  118|  45.5k|        x5 += j5;
  119|  45.5k|        x6 += j6;
  120|  45.5k|        x7 += j7;
  121|  45.5k|        x8 += j8;
  122|  45.5k|        x9 += j9;
  123|  45.5k|        x10 += j10;
  124|  45.5k|        x11 += j11;
  125|  45.5k|        x12 += j12;
  126|  45.5k|        x13 += j13;
  127|  45.5k|        x14 += j14;
  128|  45.5k|        x15 += j15;
  129|       |
  130|  45.5k|        ++j12;
  131|  45.5k|        if (!j12) ++j13;
  ------------------
  |  Branch (131:13): [True: 0, False: 45.5k]
  ------------------
  132|       |
  133|  45.5k|        WriteLE32(c + 0, x0);
  134|  45.5k|        WriteLE32(c + 4, x1);
  135|  45.5k|        WriteLE32(c + 8, x2);
  136|  45.5k|        WriteLE32(c + 12, x3);
  137|  45.5k|        WriteLE32(c + 16, x4);
  138|  45.5k|        WriteLE32(c + 20, x5);
  139|  45.5k|        WriteLE32(c + 24, x6);
  140|  45.5k|        WriteLE32(c + 28, x7);
  141|  45.5k|        WriteLE32(c + 32, x8);
  142|  45.5k|        WriteLE32(c + 36, x9);
  143|  45.5k|        WriteLE32(c + 40, x10);
  144|  45.5k|        WriteLE32(c + 44, x11);
  145|  45.5k|        WriteLE32(c + 48, x12);
  146|  45.5k|        WriteLE32(c + 52, x13);
  147|  45.5k|        WriteLE32(c + 56, x14);
  148|  45.5k|        WriteLE32(c + 60, x15);
  149|       |
  150|  45.5k|        if (blocks == 1) {
  ------------------
  |  Branch (150:13): [True: 45.5k, False: 0]
  ------------------
  151|  45.5k|            input[8] = j12;
  152|  45.5k|            input[9] = j13;
  153|  45.5k|            return;
  154|  45.5k|        }
  155|      0|        blocks -= 1;
  156|      0|        c += BLOCKLEN;
  157|      0|    }
  158|  45.5k|}

_ZN8ChaCha20C2ENSt3__14spanIKSt4byteLm18446744073709551615EEE:
   90|  38.7k|    ChaCha20(std::span<const std::byte> key) noexcept : m_aligned(key) {}

_Z8ReadLE64ITk8ByteTypeSt4byteEmPKT_:
   36|   226k|{
   37|   226k|    uint64_t x;
   38|   226k|    memcpy(&x, ptr, 8);
   39|   226k|    return le64toh_internal(x);
   40|   226k|}
_Z9WriteLE32ITk8ByteTypeSt4byteEvPT_j:
   51|   728k|{
   52|   728k|    uint32_t v = htole32_internal(x);
   53|   728k|    memcpy(ptr, &v, 4);
   54|   728k|}
_Z8ReadLE32ITk8ByteTypeSt4byteEjPKT_:
   28|   406k|{
   29|   406k|    uint32_t x;
   30|   406k|    memcpy(&x, ptr, 4);
   31|   406k|    return le32toh_internal(x);
   32|   406k|}
_Z8ReadBE64ITk8ByteTypehEmPKT_:
   81|   194k|{
   82|   194k|    uint64_t x;
   83|   194k|    memcpy(&x, ptr, 8);
   84|   194k|    return be64toh_internal(x);
   85|   194k|}
_Z9WriteBE64ITk8ByteTypehEvPT_m:
  103|   109k|{
  104|   109k|    uint64_t v = htobe64_internal(x);
  105|   109k|    memcpy(ptr, &v, 8);
  106|   109k|}

_Z6HexStrNSt3__14spanIKhLm18446744073709551615EEE:
   31|  18.0k|{
   32|  18.0k|    std::string rv(s.size() * 2, '\0');
   33|  18.0k|    static constexpr auto byte_to_hex = CreateByteToHexMap();
   34|  18.0k|    static_assert(sizeof(byte_to_hex) == 512);
   35|       |
   36|  18.0k|    char* it = rv.data();
   37|   576k|    for (uint8_t v : s) {
  ------------------
  |  Branch (37:20): [True: 576k, False: 18.0k]
  ------------------
   38|   576k|        std::memcpy(it, byte_to_hex[v].data(), 2);
   39|   576k|        it += 2;
   40|   576k|    }
   41|       |
   42|  18.0k|    assert(it == rv.data() + rv.size());
  ------------------
  |  Branch (42:5): [True: 18.0k, False: 0]
  ------------------
   43|  18.0k|    return rv;
   44|  18.0k|}

_ZN7CSHA512C2Ev:
  155|  12.1k|{
  156|  12.1k|    sha512::Initialize(s);
  157|  12.1k|}
_ZN7CSHA5125WriteEPKhm:
  160|  85.1k|{
  161|  85.1k|    const unsigned char* end = data + len;
  162|  85.1k|    size_t bufsize = bytes % 128;
  163|  85.1k|    if (bufsize && bufsize + len >= 128) {
  ------------------
  |  Branch (163:9): [True: 72.9k, False: 12.1k]
  |  Branch (163:20): [True: 12.1k, False: 60.8k]
  ------------------
  164|       |        // Fill the buffer, and process it.
  165|  12.1k|        memcpy(buf + bufsize, data, 128 - bufsize);
  166|  12.1k|        bytes += 128 - bufsize;
  167|  12.1k|        data += 128 - bufsize;
  168|  12.1k|        sha512::Transform(s, buf);
  169|  12.1k|        bufsize = 0;
  170|  12.1k|    }
  171|  85.1k|    while (end - data >= 128) {
  ------------------
  |  Branch (171:12): [True: 0, False: 85.1k]
  ------------------
  172|       |        // Process full chunks directly from the source.
  173|      0|        sha512::Transform(s, data);
  174|      0|        data += 128;
  175|      0|        bytes += 128;
  176|      0|    }
  177|  85.1k|    if (end > data) {
  ------------------
  |  Branch (177:9): [True: 72.9k, False: 12.1k]
  ------------------
  178|       |        // Fill the buffer with what remains.
  179|  72.9k|        memcpy(buf + bufsize, data, end - data);
  180|  72.9k|        bytes += end - data;
  181|  72.9k|    }
  182|  85.1k|    return *this;
  183|  85.1k|}
_ZN7CSHA5128FinalizeEPh:
  186|  12.1k|{
  187|  12.1k|    static const unsigned char pad[128] = {0x80};
  188|  12.1k|    unsigned char sizedesc[16] = {0x00};
  189|  12.1k|    WriteBE64(sizedesc + 8, bytes << 3);
  190|  12.1k|    Write(pad, 1 + ((239 - (bytes % 128)) % 128));
  191|  12.1k|    Write(sizedesc, 16);
  192|  12.1k|    WriteBE64(hash, s[0]);
  193|  12.1k|    WriteBE64(hash + 8, s[1]);
  194|  12.1k|    WriteBE64(hash + 16, s[2]);
  195|  12.1k|    WriteBE64(hash + 24, s[3]);
  196|  12.1k|    WriteBE64(hash + 32, s[4]);
  197|  12.1k|    WriteBE64(hash + 40, s[5]);
  198|  12.1k|    WriteBE64(hash + 48, s[6]);
  199|  12.1k|    WriteBE64(hash + 56, s[7]);
  200|  12.1k|}
_ZN7CSHA5125ResetEv:
  203|  12.1k|{
  204|  12.1k|    bytes = 0;
  205|  12.1k|    sha512::Initialize(s);
  206|  12.1k|    return *this;
  207|  12.1k|}
sha512.cpp:_ZN12_GLOBAL__N_16sha51210InitializeEPm:
   35|  24.3k|{
   36|  24.3k|    s[0] = 0x6a09e667f3bcc908ull;
   37|  24.3k|    s[1] = 0xbb67ae8584caa73bull;
   38|  24.3k|    s[2] = 0x3c6ef372fe94f82bull;
   39|  24.3k|    s[3] = 0xa54ff53a5f1d36f1ull;
   40|  24.3k|    s[4] = 0x510e527fade682d1ull;
   41|  24.3k|    s[5] = 0x9b05688c2b3e6c1full;
   42|  24.3k|    s[6] = 0x1f83d9abfb41bd6bull;
   43|  24.3k|    s[7] = 0x5be0cd19137e2179ull;
   44|  24.3k|}
sha512.cpp:_ZN12_GLOBAL__N_16sha5129TransformEPmPKh:
   48|  12.1k|{
   49|  12.1k|    uint64_t a = s[0], b = s[1], c = s[2], d = s[3], e = s[4], f = s[5], g = s[6], h = s[7];
   50|  12.1k|    uint64_t w0, w1, w2, w3, w4, w5, w6, w7, w8, w9, w10, w11, w12, w13, w14, w15;
   51|       |
   52|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x428a2f98d728ae22ull, w0 = ReadBE64(chunk + 0));
   53|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x7137449123ef65cdull, w1 = ReadBE64(chunk + 8));
   54|  12.1k|    Round(g, h, a, b, c, d, e, f, 0xb5c0fbcfec4d3b2full, w2 = ReadBE64(chunk + 16));
   55|  12.1k|    Round(f, g, h, a, b, c, d, e, 0xe9b5dba58189dbbcull, w3 = ReadBE64(chunk + 24));
   56|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x3956c25bf348b538ull, w4 = ReadBE64(chunk + 32));
   57|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x59f111f1b605d019ull, w5 = ReadBE64(chunk + 40));
   58|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x923f82a4af194f9bull, w6 = ReadBE64(chunk + 48));
   59|  12.1k|    Round(b, c, d, e, f, g, h, a, 0xab1c5ed5da6d8118ull, w7 = ReadBE64(chunk + 56));
   60|  12.1k|    Round(a, b, c, d, e, f, g, h, 0xd807aa98a3030242ull, w8 = ReadBE64(chunk + 64));
   61|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x12835b0145706fbeull, w9 = ReadBE64(chunk + 72));
   62|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x243185be4ee4b28cull, w10 = ReadBE64(chunk + 80));
   63|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x550c7dc3d5ffb4e2ull, w11 = ReadBE64(chunk + 88));
   64|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x72be5d74f27b896full, w12 = ReadBE64(chunk + 96));
   65|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x80deb1fe3b1696b1ull, w13 = ReadBE64(chunk + 104));
   66|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x9bdc06a725c71235ull, w14 = ReadBE64(chunk + 112));
   67|  12.1k|    Round(b, c, d, e, f, g, h, a, 0xc19bf174cf692694ull, w15 = ReadBE64(chunk + 120));
   68|       |
   69|  12.1k|    Round(a, b, c, d, e, f, g, h, 0xe49b69c19ef14ad2ull, w0 += sigma1(w14) + w9 + sigma0(w1));
   70|  12.1k|    Round(h, a, b, c, d, e, f, g, 0xefbe4786384f25e3ull, w1 += sigma1(w15) + w10 + sigma0(w2));
   71|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x0fc19dc68b8cd5b5ull, w2 += sigma1(w0) + w11 + sigma0(w3));
   72|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x240ca1cc77ac9c65ull, w3 += sigma1(w1) + w12 + sigma0(w4));
   73|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x2de92c6f592b0275ull, w4 += sigma1(w2) + w13 + sigma0(w5));
   74|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x4a7484aa6ea6e483ull, w5 += sigma1(w3) + w14 + sigma0(w6));
   75|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x5cb0a9dcbd41fbd4ull, w6 += sigma1(w4) + w15 + sigma0(w7));
   76|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x76f988da831153b5ull, w7 += sigma1(w5) + w0 + sigma0(w8));
   77|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x983e5152ee66dfabull, w8 += sigma1(w6) + w1 + sigma0(w9));
   78|  12.1k|    Round(h, a, b, c, d, e, f, g, 0xa831c66d2db43210ull, w9 += sigma1(w7) + w2 + sigma0(w10));
   79|  12.1k|    Round(g, h, a, b, c, d, e, f, 0xb00327c898fb213full, w10 += sigma1(w8) + w3 + sigma0(w11));
   80|  12.1k|    Round(f, g, h, a, b, c, d, e, 0xbf597fc7beef0ee4ull, w11 += sigma1(w9) + w4 + sigma0(w12));
   81|  12.1k|    Round(e, f, g, h, a, b, c, d, 0xc6e00bf33da88fc2ull, w12 += sigma1(w10) + w5 + sigma0(w13));
   82|  12.1k|    Round(d, e, f, g, h, a, b, c, 0xd5a79147930aa725ull, w13 += sigma1(w11) + w6 + sigma0(w14));
   83|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x06ca6351e003826full, w14 += sigma1(w12) + w7 + sigma0(w15));
   84|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x142929670a0e6e70ull, w15 += sigma1(w13) + w8 + sigma0(w0));
   85|       |
   86|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x27b70a8546d22ffcull, w0 += sigma1(w14) + w9 + sigma0(w1));
   87|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x2e1b21385c26c926ull, w1 += sigma1(w15) + w10 + sigma0(w2));
   88|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x4d2c6dfc5ac42aedull, w2 += sigma1(w0) + w11 + sigma0(w3));
   89|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x53380d139d95b3dfull, w3 += sigma1(w1) + w12 + sigma0(w4));
   90|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x650a73548baf63deull, w4 += sigma1(w2) + w13 + sigma0(w5));
   91|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x766a0abb3c77b2a8ull, w5 += sigma1(w3) + w14 + sigma0(w6));
   92|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x81c2c92e47edaee6ull, w6 += sigma1(w4) + w15 + sigma0(w7));
   93|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x92722c851482353bull, w7 += sigma1(w5) + w0 + sigma0(w8));
   94|  12.1k|    Round(a, b, c, d, e, f, g, h, 0xa2bfe8a14cf10364ull, w8 += sigma1(w6) + w1 + sigma0(w9));
   95|  12.1k|    Round(h, a, b, c, d, e, f, g, 0xa81a664bbc423001ull, w9 += sigma1(w7) + w2 + sigma0(w10));
   96|  12.1k|    Round(g, h, a, b, c, d, e, f, 0xc24b8b70d0f89791ull, w10 += sigma1(w8) + w3 + sigma0(w11));
   97|  12.1k|    Round(f, g, h, a, b, c, d, e, 0xc76c51a30654be30ull, w11 += sigma1(w9) + w4 + sigma0(w12));
   98|  12.1k|    Round(e, f, g, h, a, b, c, d, 0xd192e819d6ef5218ull, w12 += sigma1(w10) + w5 + sigma0(w13));
   99|  12.1k|    Round(d, e, f, g, h, a, b, c, 0xd69906245565a910ull, w13 += sigma1(w11) + w6 + sigma0(w14));
  100|  12.1k|    Round(c, d, e, f, g, h, a, b, 0xf40e35855771202aull, w14 += sigma1(w12) + w7 + sigma0(w15));
  101|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x106aa07032bbd1b8ull, w15 += sigma1(w13) + w8 + sigma0(w0));
  102|       |
  103|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x19a4c116b8d2d0c8ull, w0 += sigma1(w14) + w9 + sigma0(w1));
  104|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x1e376c085141ab53ull, w1 += sigma1(w15) + w10 + sigma0(w2));
  105|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x2748774cdf8eeb99ull, w2 += sigma1(w0) + w11 + sigma0(w3));
  106|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x34b0bcb5e19b48a8ull, w3 += sigma1(w1) + w12 + sigma0(w4));
  107|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x391c0cb3c5c95a63ull, w4 += sigma1(w2) + w13 + sigma0(w5));
  108|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x4ed8aa4ae3418acbull, w5 += sigma1(w3) + w14 + sigma0(w6));
  109|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x5b9cca4f7763e373ull, w6 += sigma1(w4) + w15 + sigma0(w7));
  110|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x682e6ff3d6b2b8a3ull, w7 += sigma1(w5) + w0 + sigma0(w8));
  111|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x748f82ee5defb2fcull, w8 += sigma1(w6) + w1 + sigma0(w9));
  112|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x78a5636f43172f60ull, w9 += sigma1(w7) + w2 + sigma0(w10));
  113|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x84c87814a1f0ab72ull, w10 += sigma1(w8) + w3 + sigma0(w11));
  114|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x8cc702081a6439ecull, w11 += sigma1(w9) + w4 + sigma0(w12));
  115|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x90befffa23631e28ull, w12 += sigma1(w10) + w5 + sigma0(w13));
  116|  12.1k|    Round(d, e, f, g, h, a, b, c, 0xa4506cebde82bde9ull, w13 += sigma1(w11) + w6 + sigma0(w14));
  117|  12.1k|    Round(c, d, e, f, g, h, a, b, 0xbef9a3f7b2c67915ull, w14 += sigma1(w12) + w7 + sigma0(w15));
  118|  12.1k|    Round(b, c, d, e, f, g, h, a, 0xc67178f2e372532bull, w15 += sigma1(w13) + w8 + sigma0(w0));
  119|       |
  120|  12.1k|    Round(a, b, c, d, e, f, g, h, 0xca273eceea26619cull, w0 += sigma1(w14) + w9 + sigma0(w1));
  121|  12.1k|    Round(h, a, b, c, d, e, f, g, 0xd186b8c721c0c207ull, w1 += sigma1(w15) + w10 + sigma0(w2));
  122|  12.1k|    Round(g, h, a, b, c, d, e, f, 0xeada7dd6cde0eb1eull, w2 += sigma1(w0) + w11 + sigma0(w3));
  123|  12.1k|    Round(f, g, h, a, b, c, d, e, 0xf57d4f7fee6ed178ull, w3 += sigma1(w1) + w12 + sigma0(w4));
  124|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x06f067aa72176fbaull, w4 += sigma1(w2) + w13 + sigma0(w5));
  125|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x0a637dc5a2c898a6ull, w5 += sigma1(w3) + w14 + sigma0(w6));
  126|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x113f9804bef90daeull, w6 += sigma1(w4) + w15 + sigma0(w7));
  127|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x1b710b35131c471bull, w7 += sigma1(w5) + w0 + sigma0(w8));
  128|  12.1k|    Round(a, b, c, d, e, f, g, h, 0x28db77f523047d84ull, w8 += sigma1(w6) + w1 + sigma0(w9));
  129|  12.1k|    Round(h, a, b, c, d, e, f, g, 0x32caab7b40c72493ull, w9 += sigma1(w7) + w2 + sigma0(w10));
  130|  12.1k|    Round(g, h, a, b, c, d, e, f, 0x3c9ebe0a15c9bebcull, w10 += sigma1(w8) + w3 + sigma0(w11));
  131|  12.1k|    Round(f, g, h, a, b, c, d, e, 0x431d67c49c100d4cull, w11 += sigma1(w9) + w4 + sigma0(w12));
  132|  12.1k|    Round(e, f, g, h, a, b, c, d, 0x4cc5d4becb3e42b6ull, w12 += sigma1(w10) + w5 + sigma0(w13));
  133|  12.1k|    Round(d, e, f, g, h, a, b, c, 0x597f299cfc657e2aull, w13 += sigma1(w11) + w6 + sigma0(w14));
  134|  12.1k|    Round(c, d, e, f, g, h, a, b, 0x5fcb6fab3ad6faecull, w14 + sigma1(w12) + w7 + sigma0(w15));
  135|  12.1k|    Round(b, c, d, e, f, g, h, a, 0x6c44198c4a475817ull, w15 + sigma1(w13) + w8 + sigma0(w0));
  136|       |
  137|  12.1k|    s[0] += a;
  138|  12.1k|    s[1] += b;
  139|  12.1k|    s[2] += c;
  140|  12.1k|    s[3] += d;
  141|  12.1k|    s[4] += e;
  142|  12.1k|    s[5] += f;
  143|  12.1k|    s[6] += g;
  144|  12.1k|    s[7] += h;
  145|  12.1k|}
sha512.cpp:_ZN12_GLOBAL__N_16sha5125RoundEmmmRmmmmS1_mm:
   26|   973k|{
   27|   973k|    uint64_t t1 = h + Sigma1(e) + Ch(e, f, g) + k + w;
   28|   973k|    uint64_t t2 = Sigma0(a) + Maj(a, b, c);
   29|   973k|    d += t1;
   30|   973k|    h = t1 + t2;
   31|   973k|}
sha512.cpp:_ZN12_GLOBAL__N_16sha5126Sigma1Em:
   20|   973k|uint64_t inline Sigma1(uint64_t x) { return (x >> 14 | x << 50) ^ (x >> 18 | x << 46) ^ (x >> 41 | x << 23); }
sha512.cpp:_ZN12_GLOBAL__N_16sha5122ChEmmm:
   17|   973k|uint64_t inline Ch(uint64_t x, uint64_t y, uint64_t z) { return z ^ (x & (y ^ z)); }
sha512.cpp:_ZN12_GLOBAL__N_16sha5126Sigma0Em:
   19|   973k|uint64_t inline Sigma0(uint64_t x) { return (x >> 28 | x << 36) ^ (x >> 34 | x << 30) ^ (x >> 39 | x << 25); }
sha512.cpp:_ZN12_GLOBAL__N_16sha5123MajEmmm:
   18|   973k|uint64_t inline Maj(uint64_t x, uint64_t y, uint64_t z) { return (x & y) | (z & (x | y)); }
sha512.cpp:_ZN12_GLOBAL__N_16sha5126sigma1Em:
   22|   778k|uint64_t inline sigma1(uint64_t x) { return (x >> 19 | x << 45) ^ (x >> 61 | x << 3) ^ (x >> 6); }
sha512.cpp:_ZN12_GLOBAL__N_16sha5126sigma0Em:
   21|   778k|uint64_t inline sigma0(uint64_t x) { return (x >> 1 | x << 63) ^ (x >> 8 | x << 56) ^ (x >> 7); }

_ZN9ChainCodeD2Ev:
   28|      2|    ~ChainCode() { memory_cleanse(data(), size()); }

_Z11LogInstancev:
   27|  18.0k|{
   28|       |/**
   29|       | * NOTE: the logger instances is leaked on exit. This is ugly, but will be
   30|       | * cleaned up by the OS/libc. Defining a logger as a global object doesn't work
   31|       | * since the order of destruction of static/global objects is undefined.
   32|       | * Consider if the logger gets destroyed, and then some later destructor calls
   33|       | * LogInfo, maybe indirectly, and you get a core dump at shutdown trying to
   34|       | * access the logger. When the shutdown sequence is fully audited and tested,
   35|       | * explicit destruction of these objects can be implemented by changing this
   36|       | * from a raw pointer to a std::unique_ptr.
   37|       | * Since the ~Logger() destructor is never called, the Logger class and all
   38|       | * its subclasses must have implicitly-defined destructors.
   39|       | *
   40|       | * This method of initialization was originally introduced in
   41|       | * ee3374234c60aba2cc4c5cd5cac1c0aefc2d817c.
   42|       | */
   43|  18.0k|    static BCLog::Logger* g_logger{new BCLog::Logger()};
   44|  18.0k|    return *g_logger;
   45|  18.0k|}
_ZN5BCLog6Logger8LogPrintEN4util3log5EntryE:
  435|  18.0k|{
  436|  18.0k|    STDLOCK(m_cs);
  ------------------
  |  |   41|  18.0k|#define STDLOCK(cs) StdMutex::Guard BITCOIN_UNIQUE_NAME(criticalblock){StdMutex::CheckNotHeld(cs)}
  |  |  ------------------
  |  |  |  |   11|  18.0k|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|  18.0k|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|  18.0k|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  437|  18.0k|    return LogPrint_(std::move(entry));
  438|  18.0k|}
_ZN5BCLog6Logger9LogPrint_EN4util3log5EntryE:
  442|  18.0k|{
  443|  18.0k|    if (m_buffering) {
  ------------------
  |  Branch (443:9): [True: 18.0k, False: 0]
  ------------------
  444|  18.0k|        {
  445|  18.0k|            m_cur_buffer_memusage += MemUsage(entry);
  446|  18.0k|            m_msgs_before_open.push_back(std::move(entry));
  447|  18.0k|        }
  448|       |
  449|  32.5k|        while (m_cur_buffer_memusage > m_max_buffer_memusage) {
  ------------------
  |  Branch (449:16): [True: 14.5k, False: 18.0k]
  ------------------
  450|  14.5k|            if (m_msgs_before_open.empty()) {
  ------------------
  |  Branch (450:17): [True: 0, False: 14.5k]
  ------------------
  451|      0|                m_cur_buffer_memusage = 0;
  452|      0|                break;
  453|      0|            }
  454|  14.5k|            m_cur_buffer_memusage -= MemUsage(m_msgs_before_open.front());
  455|  14.5k|            m_msgs_before_open.pop_front();
  456|  14.5k|            ++m_buffer_lines_discarded;
  457|  14.5k|        }
  458|       |
  459|  18.0k|        return;
  460|  18.0k|    }
  461|       |
  462|      0|    std::string str_prefixed{Format(entry)};
  463|      0|    bool ratelimit{false};
  464|      0|    if (entry.should_ratelimit && m_limiter) {
  ------------------
  |  Branch (464:9): [True: 0, False: 0]
  |  Branch (464:35): [True: 0, False: 0]
  ------------------
  465|      0|        auto status{m_limiter->Consume(entry.source_loc, str_prefixed)};
  466|      0|        if (status == LogRateLimiter::Status::NEWLY_SUPPRESSED) {
  ------------------
  |  Branch (466:13): [True: 0, False: 0]
  ------------------
  467|       |            // NOLINTNEXTLINE(misc-no-recursion)
  468|      0|            LogPrint_({
  469|      0|                .category = LogFlags::ALL,
  470|      0|                .level = Level::Warning,
  471|      0|                .should_ratelimit = false, // with should_ratelimit=false, this cannot lead to infinite recursion
  472|      0|                .source_loc = SourceLocation{__func__},
  473|      0|                .message = strprintf(
  ------------------
  |  | 1172|      0|#define strprintf tfm::format
  ------------------
  474|      0|                    "Excessive logging detected from %s:%d (%s): >%d bytes logged during "
  475|      0|                    "the last time window of %is. Suppressing logging to disk from this "
  476|      0|                    "source location until time window resets. Console logging "
  477|      0|                    "unaffected. Last log entry.",
  478|      0|                    entry.source_loc.file_name(), entry.source_loc.line(), entry.source_loc.function_name_short(),
  479|      0|                    m_limiter->m_max_bytes,
  480|      0|                    Ticks<std::chrono::seconds>(m_limiter->m_reset_window)),
  481|      0|            });
  482|      0|        } else if (status == LogRateLimiter::Status::STILL_SUPPRESSED) {
  ------------------
  |  Branch (482:20): [True: 0, False: 0]
  ------------------
  483|      0|            ratelimit = true;
  484|      0|        }
  485|      0|    }
  486|       |
  487|       |    // To avoid confusion caused by dropped log messages when debugging an issue,
  488|       |    // we prefix log lines with "[*]" when there are any suppressed source locations.
  489|      0|    if (m_limiter && m_limiter->SuppressionsActive()) {
  ------------------
  |  Branch (489:9): [True: 0, False: 0]
  |  Branch (489:22): [True: 0, False: 0]
  ------------------
  490|      0|        str_prefixed.insert(0, "[*] ");
  491|      0|    }
  492|       |
  493|      0|    if (m_print_to_console) {
  ------------------
  |  Branch (493:9): [True: 0, False: 0]
  ------------------
  494|       |        // print to console
  495|      0|        fwrite(str_prefixed.data(), 1, str_prefixed.size(), stdout);
  496|      0|        fflush(stdout);
  497|      0|    }
  498|      0|    for (const auto& cb : m_print_callbacks) {
  ------------------
  |  Branch (498:25): [True: 0, False: 0]
  ------------------
  499|      0|        cb(str_prefixed);
  500|      0|    }
  501|      0|    if (m_print_to_file && !ratelimit) {
  ------------------
  |  Branch (501:9): [True: 0, False: 0]
  |  Branch (501:28): [True: 0, False: 0]
  ------------------
  502|      0|        assert(m_fileout != nullptr);
  ------------------
  |  Branch (502:9): [True: 0, False: 0]
  ------------------
  503|       |
  504|       |        // reopen the log file, if requested
  505|      0|        if (m_reopen_file) {
  ------------------
  |  Branch (505:13): [True: 0, False: 0]
  ------------------
  506|      0|            m_reopen_file = false;
  507|      0|            FILE* new_fileout = fsbridge::fopen(m_file_path, "a");
  508|      0|            if (new_fileout) {
  ------------------
  |  Branch (508:17): [True: 0, False: 0]
  ------------------
  509|      0|                setbuf(new_fileout, nullptr); // unbuffered
  510|      0|                fclose(m_fileout);
  511|      0|                m_fileout = new_fileout;
  512|      0|            }
  513|      0|        }
  514|      0|        FileWriteStr(str_prefixed, m_fileout);
  515|      0|    }
  516|      0|}
_ZN4util3log3LogENS0_5EntryE:
  630|  18.0k|{
  631|  18.0k|    BCLog::Logger& logger{LogInstance()};
  632|  18.0k|    if (logger.Enabled()) {
  ------------------
  |  Branch (632:9): [True: 18.0k, False: 0]
  ------------------
  633|  18.0k|        logger.LogPrint(std::move(entry));
  634|  18.0k|    }
  635|  18.0k|}
logging.cpp:_ZL8MemUsageRKN4util3log5EntryE:
  378|  32.5k|{
  379|  32.5k|    return memusage::DynamicUsage(log.message) +
  380|  32.5k|           memusage::DynamicUsage(log.thread_name) +
  381|  32.5k|           memusage::MallocUsage(sizeof(memusage::list_node<util::log::Entry>));
  382|  32.5k|}

_ZNK5BCLog6Logger7EnabledEv:
  185|  18.0k|        {
  186|  18.0k|            STDLOCK(m_cs);
  ------------------
  |  |   41|  18.0k|#define STDLOCK(cs) StdMutex::Guard BITCOIN_UNIQUE_NAME(criticalblock){StdMutex::CheckNotHeld(cs)}
  |  |  ------------------
  |  |  |  |   11|  18.0k|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|  18.0k|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|  18.0k|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  187|  18.0k|            return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty();
  ------------------
  |  Branch (187:20): [True: 18.0k, False: 0]
  |  Branch (187:35): [True: 0, False: 0]
  |  Branch (187:57): [True: 0, False: 0]
  |  Branch (187:76): [True: 0, False: 0]
  ------------------
  188|  18.0k|        }

txgraph.cpp:_ZN8memusageL11MallocUsageEm:
   53|  7.68M|{
   54|       |    // Measured on libc6 2.19 on Linux.
   55|  7.68M|    if (alloc == 0) {
  ------------------
  |  Branch (55:9): [True: 91.0k, False: 7.59M]
  ------------------
   56|  91.0k|        return 0;
   57|  7.59M|    } else if (sizeof(void*) == 8) {
  ------------------
  |  Branch (57:16): [True: 7.59M, Folded]
  ------------------
   58|  7.59M|        return ((alloc + 31) >> 4) << 4;
   59|  7.59M|    } else if (sizeof(void*) == 4) {
  ------------------
  |  Branch (59:16): [Folded, False: 0]
  ------------------
   60|      0|        return ((alloc + 15) >> 3) << 3;
   61|      0|    } else {
   62|       |        assert(0);
  ------------------
  |  Branch (62:9): [Folded, False: 0]
  ------------------
   63|      0|    }
   64|  7.68M|}
logging.cpp:_ZN8memusageL12DynamicUsageERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEE:
   95|  65.1k|{
   96|  65.1k|    const char* s_ptr = reinterpret_cast<const char*>(&s);
   97|       |    // Don't count the dynamic memory used for string, if it resides in the
   98|       |    // "small string" optimization area (which stores data inside the object itself, up to some
   99|       |    // size; 15 bytes in modern libstdc++).
  100|  65.1k|    if (!std::less{}(s.data(), s_ptr) && !std::greater{}(s.data() + s.size(), s_ptr + sizeof(s))) {
  ------------------
  |  Branch (100:9): [True: 35.6k, False: 29.4k]
  |  Branch (100:9): [True: 32.5k, False: 32.5k]
  |  Branch (100:42): [True: 32.5k, False: 3.08k]
  ------------------
  101|  32.5k|        return 0;
  102|  32.5k|    }
  103|  32.5k|    return MallocUsage(s.capacity());
  104|  65.1k|}
logging.cpp:_ZN8memusageL11MallocUsageEm:
   53|  65.1k|{
   54|       |    // Measured on libc6 2.19 on Linux.
   55|  65.1k|    if (alloc == 0) {
  ------------------
  |  Branch (55:9): [True: 0, False: 65.1k]
  ------------------
   56|      0|        return 0;
   57|  65.1k|    } else if (sizeof(void*) == 8) {
  ------------------
  |  Branch (57:16): [True: 65.1k, Folded]
  ------------------
   58|  65.1k|        return ((alloc + 31) >> 4) << 4;
   59|  65.1k|    } else if (sizeof(void*) == 4) {
  ------------------
  |  Branch (59:16): [Folded, False: 0]
  ------------------
   60|      0|        return ((alloc + 15) >> 3) << 3;
   61|      0|    } else {
   62|       |        assert(0);
  ------------------
  |  Branch (62:9): [Folded, False: 0]
  ------------------
   63|      0|    }
   64|  65.1k|}
txgraph.cpp:_ZN8memusageL12DynamicUsageIjNSt3__19allocatorIjEEEEmRKNS1_6vectorIT_T0_EE:
   90|  1.24M|{
   91|  1.24M|    return MallocUsage(v.capacity() * sizeof(T));
   92|  1.24M|}
txgraph.cpp:_ZN8memusageL12DynamicUsageIN17cluster_linearize8DepGraphIN13bitset_detail9IntBitSetImEEE5EntryENSt3__19allocatorIS7_EEEEmRKNS8_6vectorIT_T0_EE:
   90|   623k|{
   91|   623k|    return MallocUsage(v.capacity() * sizeof(T));
   92|   623k|}
txgraph.cpp:_ZN8memusageL12DynamicUsageIN12_GLOBAL__N_111TxGraphImpl9ChunkDataENS2_10ChunkOrderEEEmRKNSt3__13setIT_T0_NS5_9allocatorIS7_EEEE:
  114|  33.8k|{
  115|  33.8k|    return MallocUsage(sizeof(stl_tree_node<X>)) * s.size();
  116|  33.8k|}

_ZN11CNetCleanupD2Ev:
 3676|      2|    {
 3677|       |#ifdef WIN32
 3678|       |        // Shutdown Windows Sockets
 3679|       |        WSACleanup();
 3680|       |#endif
 3681|      2|    }

_ZNK9prevectorILj16EhjiE9is_directEv:
  126|     16|    bool is_direct() const { return _size <= N; }
_ZN9prevectorILj16EhjiED2Ev:
  422|     16|    ~prevector() {
  423|     16|        if (!is_direct()) {
  ------------------
  |  Branch (423:13): [True: 0, False: 16]
  ------------------
  424|      0|            free(_union.indirect_contents.indirect);
  425|      0|            _union.indirect_contents.indirect = nullptr;
  426|      0|        }
  427|     16|    }
_ZNK9prevectorILj36EhjiE9is_directEv:
  126|     14|    bool is_direct() const { return _size <= N; }
_ZN9prevectorILj36EhjiED2Ev:
  422|     14|    ~prevector() {
  423|     14|        if (!is_direct()) {
  ------------------
  |  Branch (423:13): [True: 0, False: 14]
  ------------------
  424|      0|            free(_union.indirect_contents.indirect);
  425|      0|            _union.indirect_contents.indirect = nullptr;
  426|      0|        }
  427|     14|    }

_Z30MakeRandDeterministicDANGEROUSRK7uint256:
  596|  18.0k|{
  597|  18.0k|    GetRNGState().MakeDeterministic(seed);
  598|  18.0k|}
_Z12GetRandBytesNSt3__14spanIhLm18446744073709551615EEE:
  602|  12.1k|{
  603|  12.1k|    g_used_g_prng = true;
  604|  12.1k|    ProcRand(bytes.data(), bytes.size(), RNGLevel::FAST, /*always_use_real_rng=*/false);
  605|  12.1k|}
_ZN17FastRandomContext10RandomSeedEv:
  620|  12.1k|{
  621|  12.1k|    uint256 seed = GetRandHash();
  622|  12.1k|    rng.SetKey(MakeByteSpan(seed));
  623|  12.1k|    requires_seed = false;
  624|  12.1k|}
_ZN17FastRandomContextC2Eb:
  689|  20.6k|FastRandomContext::FastRandomContext(bool fDeterministic) noexcept : requires_seed(!fDeterministic), rng(ZERO_KEY)
  690|  20.6k|{
  691|       |    // Note that despite always initializing with ZERO_KEY, requires_seed is set to true if not
  692|       |    // fDeterministic. That means the rng will be reinitialized with a secure random key upon first
  693|       |    // use.
  694|  20.6k|}
random.cpp:_ZN12_GLOBAL__N_111GetRNGStateEv:
  451|  30.1k|{
  452|       |    // This idiom relies on the guarantee that static variable are initialized
  453|       |    // on first call, even when multiple parallel calls are permitted.
  454|  30.1k|    static std::vector<RNGState, secure_allocator<RNGState>> g_rng(1);
  455|  30.1k|    return g_rng[0];
  456|  30.1k|}
random.cpp:_ZN12_GLOBAL__N_18RNGStateD2Ev:
  367|      2|    ~RNGState() = default;
random.cpp:_ZN12_GLOBAL__N_18RNGState17MakeDeterministicERK7uint256:
  400|  18.0k|    {
  401|  18.0k|        LOCK(m_mutex);
  ------------------
  |  |  268|  18.0k|#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
  |  |  ------------------
  |  |  |  |   11|  18.0k|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|  18.0k|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|  18.0k|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  402|  18.0k|        m_deterministic_prng.emplace(MakeByteSpan(seed));
  403|  18.0k|    }
random.cpp:_ZN12_GLOBAL__N_18ProcRandEPhiNS_8RNGLevelEb:
  563|  12.1k|{
  564|       |    // Make sure the RNG is initialized first (as all Seed* function possibly need hwrand to be available).
  565|  12.1k|    RNGState& rng = GetRNGState();
  566|       |
  567|  12.1k|    assert(num <= 32);
  ------------------
  |  Branch (567:5): [True: 12.1k, False: 0]
  ------------------
  568|       |
  569|  12.1k|    CSHA512 hasher;
  570|  12.1k|    switch (level) {
  ------------------
  |  Branch (570:13): [True: 12.1k, False: 0]
  ------------------
  571|  12.1k|    case RNGLevel::FAST:
  ------------------
  |  Branch (571:5): [True: 12.1k, False: 0]
  ------------------
  572|  12.1k|        SeedFast(hasher);
  573|  12.1k|        break;
  574|      0|    case RNGLevel::SLOW:
  ------------------
  |  Branch (574:5): [True: 0, False: 12.1k]
  ------------------
  575|      0|        SeedSlow(hasher, rng);
  576|      0|        break;
  577|      0|    case RNGLevel::PERIODIC:
  ------------------
  |  Branch (577:5): [True: 0, False: 12.1k]
  ------------------
  578|      0|        SeedPeriodic(hasher, rng);
  579|      0|        break;
  580|  12.1k|    }
  581|       |
  582|       |    // Combine with and update state
  583|  12.1k|    if (!rng.MixExtract(out, num, std::move(hasher), false, always_use_real_rng)) {
  ------------------
  |  Branch (583:9): [True: 0, False: 12.1k]
  ------------------
  584|       |        // On the first invocation, also seed with SeedStartup().
  585|      0|        CSHA512 startup_hasher;
  586|      0|        SeedStartup(startup_hasher, rng);
  587|      0|        rng.MixExtract(out, num, std::move(startup_hasher), true, always_use_real_rng);
  588|      0|    }
  589|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_18SeedFastER7CSHA512:
  470|  12.1k|{
  471|  12.1k|    unsigned char buffer[32];
  472|       |
  473|       |    // Stack pointer to indirectly commit to thread/callstack
  474|  12.1k|    const unsigned char* ptr = buffer;
  475|  12.1k|    hasher.Write((const unsigned char*)&ptr, sizeof(ptr));
  476|       |
  477|       |    // Hardware randomness is very fast when available; use it always.
  478|  12.1k|    SeedHardwareFast(hasher);
  479|       |
  480|       |    // High-precision timestamp
  481|  12.1k|    SeedTimestamp(hasher);
  482|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_116SeedHardwareFastER7CSHA512:
  199|  12.1k|void SeedHardwareFast(CSHA512& hasher) noexcept {
  200|  12.1k|#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__)
  201|  12.1k|    if (g_rdrand_supported) {
  ------------------
  |  Branch (201:9): [True: 12.1k, False: 0]
  ------------------
  202|  12.1k|        uint64_t out = GetRdRand();
  203|  12.1k|        hasher.Write((const unsigned char*)&out, sizeof(out));
  204|  12.1k|        return;
  205|  12.1k|    }
  206|  12.1k|#endif
  207|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_19GetRdRandEv:
  122|  12.1k|{
  123|       |    // RdRand may very rarely fail. Invoke it up to 10 times in a loop to reduce this risk.
  124|       |#ifdef __i386__
  125|       |    uint8_t ok = 0;
  126|       |    // Initialize to 0 to silence a compiler warning that r1 or r2 may be used
  127|       |    // uninitialized. Even if rdrand fails (!ok) it will set the output to 0,
  128|       |    // but there is no way that the compiler could know that.
  129|       |    uint32_t r1 = 0, r2 = 0;
  130|       |    for (int i = 0; i < 10; ++i) {
  131|       |        __asm__ volatile (".byte 0x0f, 0xc7, 0xf0; setc %1" : "=a"(r1), "=q"(ok) :: "cc"); // rdrand %eax
  132|       |        if (ok) break;
  133|       |    }
  134|       |    for (int i = 0; i < 10; ++i) {
  135|       |        __asm__ volatile (".byte 0x0f, 0xc7, 0xf0; setc %1" : "=a"(r2), "=q"(ok) :: "cc"); // rdrand %eax
  136|       |        if (ok) break;
  137|       |    }
  138|       |    return (((uint64_t)r2) << 32) | r1;
  139|       |#elif defined(__x86_64__) || defined(__amd64__)
  140|       |    uint8_t ok = 0;
  141|  12.1k|    uint64_t r1 = 0; // See above why we initialize to 0.
  142|  12.1k|    for (int i = 0; i < 10; ++i) {
  ------------------
  |  Branch (142:21): [True: 12.1k, False: 0]
  ------------------
  143|  12.1k|        __asm__ volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %1" : "=a"(r1), "=q"(ok) :: "cc"); // rdrand %rax
  144|  12.1k|        if (ok) break;
  ------------------
  |  Branch (144:13): [True: 12.1k, False: 0]
  ------------------
  145|  12.1k|    }
  146|  12.1k|    return r1;
  147|       |#else
  148|       |#error "RdRand is only supported on x86 and x86_64"
  149|       |#endif
  150|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_113SeedTimestampER7CSHA512:
  464|  12.1k|{
  465|  12.1k|    int64_t perfcounter = GetPerformanceCounter();
  466|  12.1k|    hasher.Write((const unsigned char*)&perfcounter, sizeof(perfcounter));
  467|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_121GetPerformanceCounterEv:
   61|  12.1k|{
   62|       |    // Read the hardware time stamp counter when available.
   63|       |    // See https://en.wikipedia.org/wiki/Time_Stamp_Counter for more information.
   64|       |#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
   65|       |    return __rdtsc();
   66|       |#elif !defined(_MSC_VER) && defined(__i386__)
   67|       |    uint64_t r = 0;
   68|       |    __asm__ volatile ("rdtsc" : "=A"(r)); // Constrain the r variable to the eax:edx pair.
   69|       |    return r;
   70|       |#elif !defined(_MSC_VER) && (defined(__x86_64__) || defined(__amd64__))
   71|       |    uint64_t r1 = 0, r2 = 0;
   72|  12.1k|    __asm__ volatile ("rdtsc" : "=a"(r1), "=d"(r2)); // Constrain r1 to rax and r2 to rdx.
   73|  12.1k|    return (r2 << 32) | r1;
   74|       |#else
   75|       |    // Fall back to using standard library clock (usually microsecond or nanosecond precision)
   76|       |    return std::chrono::high_resolution_clock::now().time_since_epoch().count();
   77|       |#endif
   78|  12.1k|}
random.cpp:_ZN12_GLOBAL__N_18RNGState10MixExtractEPhmO7CSHA512bb:
  413|  12.1k|    {
  414|  12.1k|        assert(num <= 32);
  ------------------
  |  Branch (414:9): [True: 12.1k, False: 0]
  ------------------
  415|  12.1k|        unsigned char buf[64];
  416|  12.1k|        static_assert(sizeof(buf) == CSHA512::OUTPUT_SIZE, "Buffer needs to have hasher's output size");
  417|  12.1k|        bool ret;
  418|  12.1k|        {
  419|  12.1k|            LOCK(m_mutex);
  ------------------
  |  |  268|  12.1k|#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
  |  |  ------------------
  |  |  |  |   11|  12.1k|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|  12.1k|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|  12.1k|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  420|  12.1k|            ret = (m_strongly_seeded |= strong_seed);
  421|       |            // Write the current state of the RNG into the hasher
  422|  12.1k|            hasher.Write(m_state, 32);
  423|       |            // Write a new counter number into the state
  424|  12.1k|            hasher.Write((const unsigned char*)&m_counter, sizeof(m_counter));
  425|  12.1k|            ++m_counter;
  426|       |            // Finalize the hasher
  427|  12.1k|            hasher.Finalize(buf);
  428|       |            // Store the last 32 bytes of the hash output as new RNG state.
  429|  12.1k|            memcpy(m_state, buf + 32, 32);
  430|       |            // Handle requests for deterministic randomness.
  431|  12.1k|            if (!always_use_real_rng && m_deterministic_prng.has_value()) [[unlikely]] {
  ------------------
  |  Branch (431:17): [True: 12.1k, False: 0]
  |  Branch (431:41): [True: 12.1k, False: 0]
  ------------------
  432|       |                // Overwrite the beginning of buf, which will be used for output.
  433|  12.1k|                m_deterministic_prng->Keystream(std::as_writable_bytes(std::span{buf, num}));
  434|       |                // Do not require strong seeding for deterministic output.
  435|  12.1k|                ret = true;
  436|  12.1k|            }
  437|  12.1k|        }
  438|       |        // If desired, copy (up to) the first 32 bytes of the hash output as output.
  439|  12.1k|        if (num) {
  ------------------
  |  Branch (439:13): [True: 12.1k, False: 0]
  ------------------
  440|  12.1k|            assert(out != nullptr);
  ------------------
  |  Branch (440:13): [True: 12.1k, False: 0]
  ------------------
  441|  12.1k|            memcpy(out, buf, num);
  442|  12.1k|        }
  443|       |        // Best effort cleanup of internal state
  444|  12.1k|        hasher.Reset();
  445|  12.1k|        memory_cleanse(buf, 64);
  446|  12.1k|        return ret;
  447|  12.1k|    }

_ZN11RandomMixinI21InsecureRandomContextEclEv:
  376|  2.61M|    inline uint64_t operator()() noexcept { return Impl().rand64(); }
_ZN11RandomMixinI21InsecureRandomContextE9randrangeITkNSt3__18integralEiEET_S4_:
  255|  1.78M|    {
  256|  1.78M|        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
  257|  1.78M|        Assume(range > 0);
  ------------------
  |  |  128|  1.78M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  258|  1.78M|        uint64_t maxval = range - 1U;
  259|  1.78M|        int bits = std::bit_width(maxval);
  260|  2.37M|        while (true) {
  ------------------
  |  Branch (260:16): [True: 2.37M, Folded]
  ------------------
  261|  2.37M|            uint64_t ret = Impl().randbits(bits);
  262|  2.37M|            if (ret <= maxval) return ret;
  ------------------
  |  Branch (262:17): [True: 1.78M, False: 594k]
  ------------------
  263|  2.37M|        }
  264|  1.78M|    }
_ZN11RandomMixinI21InsecureRandomContextE9randrangeITkNSt3__18integralEmEET_S4_:
  255|  4.57M|    {
  256|  4.57M|        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
  257|  4.57M|        Assume(range > 0);
  ------------------
  |  |  128|  4.57M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  258|  4.57M|        uint64_t maxval = range - 1U;
  259|  4.57M|        int bits = std::bit_width(maxval);
  260|  6.25M|        while (true) {
  ------------------
  |  Branch (260:16): [True: 6.25M, Folded]
  ------------------
  261|  6.25M|            uint64_t ret = Impl().randbits(bits);
  262|  6.25M|            if (ret <= maxval) return ret;
  ------------------
  |  Branch (262:17): [True: 4.57M, False: 1.68M]
  ------------------
  263|  6.25M|        }
  264|  4.57M|    }
_ZN11RandomMixinI17FastRandomContextEC2Ev:
  195|  20.6k|    constexpr RandomMixin() noexcept = default;
_ZN21InsecureRandomContextC2Em:
  439|   245k|        : m_s0(SplitMix64(seedval)), m_s1(SplitMix64(seedval)) {}
_ZN11RandomMixinI21InsecureRandomContextEC2Ev:
  195|   245k|    constexpr RandomMixin() noexcept = default;
_ZN21InsecureRandomContext10SplitMix64ERm:
  430|   490k|    {
  431|   490k|        uint64_t z = (seedval += 0x9e3779b97f4a7c15);
  432|   490k|        z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
  433|   490k|        z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
  434|   490k|        return z ^ (z >> 31);
  435|   490k|    }
_ZN21InsecureRandomContext6rand64Ev:
  449|  7.54M|    {
  450|  7.54M|        uint64_t s0 = m_s0, s1 = m_s1;
  451|  7.54M|        const uint64_t result = std::rotl(s0 + s1, 17) + s0;
  452|  7.54M|        s1 ^= s0;
  453|  7.54M|        m_s0 = std::rotl(s0, 49) ^ s1 ^ (s1 << 21);
  454|  7.54M|        m_s1 = std::rotl(s1, 28);
  455|  7.54M|        return result;
  456|  7.54M|    }
_ZN11RandomMixinI21InsecureRandomContextE9randrangeITkNSt3__18integralEjEET_S4_:
  255|  9.53M|    {
  256|  9.53M|        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
  257|  9.53M|        Assume(range > 0);
  ------------------
  |  |  128|  9.53M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  258|  9.53M|        uint64_t maxval = range - 1U;
  259|  9.53M|        int bits = std::bit_width(maxval);
  260|  10.2M|        while (true) {
  ------------------
  |  Branch (260:16): [True: 10.2M, Folded]
  ------------------
  261|  10.2M|            uint64_t ret = Impl().randbits(bits);
  262|  10.2M|            if (ret <= maxval) return ret;
  ------------------
  |  Branch (262:17): [True: 9.53M, False: 739k]
  ------------------
  263|  10.2M|        }
  264|  9.53M|    }
_ZN11RandomMixinI21InsecureRandomContextE4ImplEv:
  185|  24.9M|    RandomNumberGenerator auto& Impl() noexcept { return static_cast<T&>(*this); }
_ZN11RandomMixinI21InsecureRandomContextE8randbitsEi:
  205|  20.2M|    {
  206|  20.2M|        Assume(bits <= 64);
  ------------------
  |  |  128|  20.2M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  207|       |        // Requests for the full 64 bits are passed through.
  208|  20.2M|        if (bits == 64) return Impl().rand64();
  ------------------
  |  Branch (208:13): [True: 0, False: 20.2M]
  ------------------
  209|  20.2M|        uint64_t ret;
  210|  20.2M|        if (bits <= bitbuf_size) {
  ------------------
  |  Branch (210:13): [True: 19.4M, False: 803k]
  ------------------
  211|       |            // If there is enough entropy left in bitbuf, return its bottom bits bits.
  212|  19.4M|            ret = bitbuf;
  213|  19.4M|            bitbuf >>= bits;
  214|  19.4M|            bitbuf_size -= bits;
  215|  19.4M|        } else {
  216|       |            // If not, return all of bitbuf, supplemented with the (bits - bitbuf_size) bottom
  217|       |            // bits of a newly generated 64-bit number on top. The remainder of that generated
  218|       |            // number becomes the new bitbuf.
  219|   803k|            uint64_t gen = Impl().rand64();
  220|   803k|            ret = (gen << bitbuf_size) | bitbuf;
  221|   803k|            bitbuf = gen >> (bits - bitbuf_size);
  222|   803k|            bitbuf_size = 64 + bitbuf_size - bits;
  223|   803k|        }
  224|       |        // Return the bottom bits bits of ret.
  225|  20.2M|        return ret & ((uint64_t{1} << bits) - 1);
  226|  20.2M|    }
_ZN11RandomMixinI21InsecureRandomContextE8randboolEv:
  325|  1.05M|    bool randbool() noexcept { return Impl().template randbits<1>(); }
_ZN11RandomMixinI21InsecureRandomContextE8randbitsILi1EEEmv:
  231|  1.54M|    {
  232|  1.54M|        static_assert(Bits >= 0 && Bits <= 64);
  233|       |        if constexpr (Bits == 64) {
  234|       |            return Impl().rand64();
  235|  1.54M|        } else {
  236|  1.54M|            uint64_t ret;
  237|  1.54M|            if (Bits <= bitbuf_size) {
  ------------------
  |  Branch (237:17): [True: 1.36M, False: 182k]
  ------------------
  238|  1.36M|                ret = bitbuf;
  239|  1.36M|                bitbuf >>= Bits;
  240|  1.36M|                bitbuf_size -= Bits;
  241|  1.36M|            } else {
  242|   182k|                uint64_t gen = Impl().rand64();
  243|   182k|                ret = (gen << bitbuf_size) | bitbuf;
  244|   182k|                bitbuf = gen >> (Bits - bitbuf_size);
  245|   182k|                bitbuf_size = 64 + bitbuf_size - Bits;
  246|   182k|            }
  247|  1.54M|            constexpr uint64_t MASK = (uint64_t{1} << Bits) - 1;
  248|  1.54M|            return ret & MASK;
  249|  1.54M|        }
  250|  1.54M|    }
_ZN11RandomMixinI21InsecureRandomContextE9randrangeITkNSt3__18integralEhEET_S4_:
  255|  1.17M|    {
  256|  1.17M|        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
  257|  1.17M|        Assume(range > 0);
  ------------------
  |  |  128|  1.17M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  258|  1.17M|        uint64_t maxval = range - 1U;
  259|  1.17M|        int bits = std::bit_width(maxval);
  260|  1.38M|        while (true) {
  ------------------
  |  Branch (260:16): [True: 1.38M, Folded]
  ------------------
  261|  1.38M|            uint64_t ret = Impl().randbits(bits);
  262|  1.38M|            if (ret <= maxval) return ret;
  ------------------
  |  Branch (262:17): [True: 1.17M, False: 203k]
  ------------------
  263|  1.38M|        }
  264|  1.17M|    }
_ZN11RandomMixinI17FastRandomContextE9randrangeITkNSt3__18integralEmEET_S4_:
  255|  17.1k|    {
  256|  17.1k|        static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
  257|  17.1k|        Assume(range > 0);
  ------------------
  |  |  128|  17.1k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  258|  17.1k|        uint64_t maxval = range - 1U;
  259|  17.1k|        int bits = std::bit_width(maxval);
  260|  18.8k|        while (true) {
  ------------------
  |  Branch (260:16): [True: 18.8k, Folded]
  ------------------
  261|  18.8k|            uint64_t ret = Impl().randbits(bits);
  262|  18.8k|            if (ret <= maxval) return ret;
  ------------------
  |  Branch (262:17): [True: 17.1k, False: 1.64k]
  ------------------
  263|  18.8k|        }
  264|  17.1k|    }
_Z11GetRandHashv:
  464|  12.1k|{
  465|  12.1k|    uint256 hash;
  466|  12.1k|    GetRandBytes(hash);
  467|  12.1k|    return hash;
  468|  12.1k|}
_ZN11RandomMixinI17FastRandomContextE4ImplEv:
  185|  20.6k|    RandomNumberGenerator auto& Impl() noexcept { return static_cast<T&>(*this); }
_ZN11RandomMixinI17FastRandomContextE8randbitsEi:
  205|  18.8k|    {
  206|  18.8k|        Assume(bits <= 64);
  ------------------
  |  |  128|  18.8k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  207|       |        // Requests for the full 64 bits are passed through.
  208|  18.8k|        if (bits == 64) return Impl().rand64();
  ------------------
  |  Branch (208:13): [True: 0, False: 18.8k]
  ------------------
  209|  18.8k|        uint64_t ret;
  210|  18.8k|        if (bits <= bitbuf_size) {
  ------------------
  |  Branch (210:13): [True: 17.0k, False: 1.80k]
  ------------------
  211|       |            // If there is enough entropy left in bitbuf, return its bottom bits bits.
  212|  17.0k|            ret = bitbuf;
  213|  17.0k|            bitbuf >>= bits;
  214|  17.0k|            bitbuf_size -= bits;
  215|  17.0k|        } else {
  216|       |            // If not, return all of bitbuf, supplemented with the (bits - bitbuf_size) bottom
  217|       |            // bits of a newly generated 64-bit number on top. The remainder of that generated
  218|       |            // number becomes the new bitbuf.
  219|  1.80k|            uint64_t gen = Impl().rand64();
  220|  1.80k|            ret = (gen << bitbuf_size) | bitbuf;
  221|  1.80k|            bitbuf = gen >> (bits - bitbuf_size);
  222|  1.80k|            bitbuf_size = 64 + bitbuf_size - bits;
  223|  1.80k|        }
  224|       |        // Return the bottom bits bits of ret.
  225|  18.8k|        return ret & ((uint64_t{1} << bits) - 1);
  226|  18.8k|    }
_ZN17FastRandomContext6rand64Ev:
  405|   226k|    {
  406|   226k|        if (requires_seed) RandomSeed();
  ------------------
  |  Branch (406:13): [True: 12.1k, False: 214k]
  ------------------
  407|   226k|        std::array<std::byte, 8> buf;
  408|   226k|        rng.Keystream(buf);
  409|   226k|        return ReadLE64(buf.data());
  410|   226k|    }

_ZN20BaseSignatureCheckerD2Ev:
  298|      2|    virtual ~BaseSignatureChecker() = default;

_ZN20BaseSignatureCreatorD2Ev:
   41|      4|    virtual ~BaseSignatureCreator() = default;

_ZN15SigningProviderD2Ev:
  170|      2|    virtual ~SigningProvider() = default;

_Z12MakeByteSpanI7uint256EDaRKT_:
   85|  30.1k|{
   86|  30.1k|    return std::as_bytes(std::span{v});
   87|  30.1k|}

random.cpp:_ZN16secure_allocatorIN12_GLOBAL__N_18RNGStateEE10deallocateEPS1_m:
   37|      2|    {
   38|      2|        if (p != nullptr) {
  ------------------
  |  Branch (38:13): [True: 2, False: 0]
  ------------------
   39|      2|            memory_cleanse(p, sizeof(T) * n);
   40|      2|        }
   41|      2|        LockedPoolManager::Instance().free(p);
   42|      2|    }

_Z14memory_cleansePvm:
   15|   101k|{
   16|       |#if defined(WIN32)
   17|       |    /* SecureZeroMemory is guaranteed not to be optimized out. */
   18|       |    SecureZeroMemory(ptr, len);
   19|       |#else
   20|   101k|    std::memset(ptr, 0, len);
   21|       |
   22|       |    /* Memory barrier that scares the compiler away from optimizing out the memset.
   23|       |     *
   24|       |     * Quoting Adam Langley <agl@google.com> in commit ad1907fe73334d6c696c8539646c21b11178f20f
   25|       |     * in BoringSSL (ISC License):
   26|       |     *    As best as we can tell, this is sufficient to break any optimisations that
   27|       |     *    might try to eliminate "superfluous" memsets.
   28|       |     * This method is used in memzero_explicit() the Linux kernel, too. Its advantage is that it
   29|       |     * is pretty efficient because the compiler can still implement the memset() efficiently,
   30|       |     * just not remove it entirely. See "Dead Store Elimination (Still) Considered Harmful" by
   31|       |     * Yang et al. (USENIX Security 2017) for more background.
   32|       |     */
   33|   101k|    __asm__ __volatile__("" : : "r"(ptr) : "memory");
   34|   101k|#endif
   35|   101k|}

_ZN5ArenaD2Ev:
   48|      2|Arena::~Arena() = default;
_ZN5Arena4freeEPv:
   87|      2|{
   88|       |    // Freeing the nullptr pointer is OK.
   89|      2|    if (ptr == nullptr) {
  ------------------
  |  Branch (89:9): [True: 0, False: 2]
  ------------------
   90|      0|        return;
   91|      0|    }
   92|       |
   93|       |    // Remove chunk from used map
   94|      2|    auto i = chunks_used.find(ptr);
   95|      2|    if (i == chunks_used.end()) {
  ------------------
  |  Branch (95:9): [True: 0, False: 2]
  ------------------
   96|      0|        throw std::runtime_error("Arena: invalid or double free");
   97|      0|    }
   98|      2|    auto freed = std::make_pair(static_cast<char*>(i->first), i->second);
   99|      2|    chunks_used.erase(i);
  100|       |
  101|       |    // coalesce freed with previous chunk
  102|      2|    auto prev = chunks_free_end.find(freed.first);
  103|      2|    if (prev != chunks_free_end.end()) {
  ------------------
  |  Branch (103:9): [True: 2, False: 0]
  ------------------
  104|      2|        freed.first -= prev->second->first;
  105|      2|        freed.second += prev->second->first;
  106|      2|        size_to_free_chunk.erase(prev->second);
  107|      2|        chunks_free_end.erase(prev);
  108|      2|    }
  109|       |
  110|       |    // coalesce freed with chunk after freed
  111|      2|    auto next = chunks_free.find(freed.first + freed.second);
  112|      2|    if (next != chunks_free.end()) {
  ------------------
  |  Branch (112:9): [True: 0, False: 2]
  ------------------
  113|      0|        freed.second += next->second->first;
  114|      0|        size_to_free_chunk.erase(next->second);
  115|      0|        chunks_free.erase(next);
  116|      0|    }
  117|       |
  118|       |    // Add/set space with coalesced free chunk
  119|      2|    auto it = size_to_free_chunk.emplace(freed.second, freed.first);
  120|      2|    chunks_free[freed.first] = it;
  121|      2|    chunks_free_end[freed.first + freed.second] = it;
  122|      2|}
_ZN24PosixLockedPageAllocator10FreeLockedEPvm:
  254|      2|{
  255|      2|    len = align_up(len, page_size);
  256|      2|    memory_cleanse(addr, len);
  257|      2|    munlock(addr, len);
  258|      2|    munmap(addr, len);
  259|      2|}
_ZN10LockedPoolD2Ev:
  283|      2|LockedPool::~LockedPool() = default;
_ZN10LockedPool4freeEPv:
  308|      2|{
  309|      2|    std::lock_guard<std::mutex> lock(mutex);
  310|       |    // TODO we can do better than this linear search by keeping a map of arena
  311|       |    // extents to arena, and looking up the address.
  312|      2|    for (auto &arena: arenas) {
  ------------------
  |  Branch (312:21): [True: 2, False: 0]
  ------------------
  313|      2|        if (arena.addressInArena(ptr)) {
  ------------------
  |  Branch (313:13): [True: 2, False: 0]
  ------------------
  314|      2|            arena.free(ptr);
  315|      2|            return;
  316|      2|        }
  317|      2|    }
  318|      0|    throw std::runtime_error("LockedPool: invalid address not pointing to any arena");
  319|      2|}
_ZN10LockedPool15LockedPageArenaD2Ev:
  370|      2|{
  371|      2|    allocator->FreeLocked(base, size);
  372|      2|}
_ZN17LockedPoolManager8InstanceEv:
  405|      2|{
  406|      2|    static std::once_flag init_flag;
  407|      2|    std::call_once(init_flag, LockedPoolManager::CreateInstance);
  408|      2|    return *LockedPoolManager::_instance;
  409|      2|}
lockedpool.cpp:_ZL8align_upmm:
   32|      2|{
   33|      2|    return (x + align - 1) & ~(align - 1);
   34|      2|}

_ZNK5Arena14addressInArenaEPv:
   90|      2|    bool addressInArena(void *ptr) const { return ptr >= base && ptr < end; }
  ------------------
  |  Branch (90:51): [True: 2, False: 0]
  |  Branch (90:66): [True: 2, False: 0]
  ------------------
_ZN19LockedPageAllocatorD2Ev:
   22|      2|    virtual ~LockedPageAllocator() = default;

_ZN14AnnotatedMixinINSt3__115recursive_mutexEED2Ev:
   96|      2|    ~AnnotatedMixin() {
   97|      2|        DeleteLock((void*)this);
   98|      2|    }
_ZN14AnnotatedMixinINSt3__15mutexEED2Ev:
   96|     64|    ~AnnotatedMixin() {
   97|     64|        DeleteLock((void*)this);
   98|     64|    }
_Z10DeleteLockPv:
   74|     66|inline void DeleteLock(void* cs) {}
_Z17MaybeCheckNotHeldR14AnnotatedMixinINSt3__15mutexEE:
  258|  30.2k|inline Mutex& MaybeCheckNotHeld(Mutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
_ZN10UniqueLockI14AnnotatedMixinINSt3__15mutexEEEC2ERS3_PKcS7_ib:
  181|  30.2k|    UniqueLock(MutexType& mutexIn, const char* pszName, const char* pszFile, int nLine, bool fTry = false) EXCLUSIVE_LOCK_FUNCTION(mutexIn) : Base(mutexIn, std::defer_lock)
  182|  30.2k|    {
  183|  30.2k|        if (fTry)
  ------------------
  |  Branch (183:13): [True: 0, False: 30.2k]
  ------------------
  184|      0|            TryEnter(pszName, pszFile, nLine);
  185|  30.2k|        else
  186|  30.2k|            Enter(pszName, pszFile, nLine);
  187|  30.2k|    }
_Z13EnterCriticalINSt3__15mutexEEvPKcS3_iPT_b:
   67|  30.2k|inline void EnterCritical(const char* pszName, const char* pszFile, int nLine, MutexType* cs, bool fTry = false) {}
_Z13LeaveCriticalv:
   68|  30.2k|inline void LeaveCritical() {}
_ZN10UniqueLockI14AnnotatedMixinINSt3__15mutexEEE5EnterEPKcS6_i:
  159|  30.2k|    {
  160|  30.2k|        EnterCritical(pszName, pszFile, nLine, Base::mutex());
  161|       |#ifdef DEBUG_LOCKCONTENTION
  162|       |        if (!Base::try_lock()) {
  163|       |            ContendedLock(pszName, pszFile, nLine, static_cast<Base&>(*this));
  164|       |        }
  165|       |#else
  166|  30.2k|        Base::lock();
  167|  30.2k|#endif
  168|  30.2k|    }
_ZN10UniqueLockI14AnnotatedMixinINSt3__15mutexEEED2Ev:
  201|  30.2k|    {
  202|  30.2k|        if (Base::owns_lock())
  ------------------
  |  Branch (202:13): [True: 30.2k, False: 0]
  ------------------
  203|  30.2k|            LeaveCritical();
  204|  30.2k|    }

_ZN18FuzzedDataProvider15ConsumeIntegralImEET_v:
  195|  18.0k|template <typename T> T FuzzedDataProvider::ConsumeIntegral() {
  196|  18.0k|  return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
  197|  18.0k|                                std::numeric_limits<T>::max());
  198|  18.0k|}
_ZN18FuzzedDataProvider15remaining_bytesEv:
   85|  1.51M|  size_t remaining_bytes() { return remaining_bytes_; }
_ZN18FuzzedDataProviderC2EPKhm:
   37|  18.0k|      : data_ptr_(data), remaining_bytes_(size) {}
_ZN18FuzzedDataProvider15ConsumeIntegralIhEET_v:
  195|  2.26M|template <typename T> T FuzzedDataProvider::ConsumeIntegral() {
  196|  2.26M|  return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
  197|  2.26M|                                std::numeric_limits<T>::max());
  198|  2.26M|}
_ZN18FuzzedDataProvider22ConsumeIntegralInRangeIhEET_S1_S1_:
  205|  2.26M|T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
  206|  2.26M|  static_assert(std::is_integral_v<T>, "An integral type is required.");
  207|  2.26M|  static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
  208|       |
  209|  2.26M|  if (min > max)
  ------------------
  |  Branch (209:7): [True: 0, False: 2.26M]
  ------------------
  210|      0|    abort();
  211|       |
  212|       |  // Use the biggest type possible to hold the range and the result.
  213|  2.26M|  uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
  214|  2.26M|  uint64_t result = 0;
  215|  2.26M|  size_t offset = 0;
  216|       |
  217|  4.52M|  while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
  ------------------
  |  Branch (217:10): [True: 2.26M, False: 2.26M]
  |  Branch (217:43): [True: 2.26M, False: 0]
  ------------------
  218|  2.26M|         remaining_bytes_ != 0) {
  ------------------
  |  Branch (218:10): [True: 2.26M, False: 1.14k]
  ------------------
  219|       |    // Pull bytes off the end of the seed data. Experimentally, this seems to
  220|       |    // allow the fuzzer to more easily explore the input space. This makes
  221|       |    // sense, since it works by modifying inputs that caused new code to run,
  222|       |    // and this data is often used to encode length of data read by
  223|       |    // |ConsumeBytes|. Separating out read lengths makes it easier modify the
  224|       |    // contents of the data that is actually read.
  225|  2.26M|    --remaining_bytes_;
  226|  2.26M|    result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
  227|  2.26M|    offset += CHAR_BIT;
  228|  2.26M|  }
  229|       |
  230|       |  // Avoid division by 0, in case |range + 1| results in overflow.
  231|  2.26M|  if (range != std::numeric_limits<decltype(range)>::max())
  ------------------
  |  Branch (231:7): [True: 2.26M, False: 0]
  ------------------
  232|  2.26M|    result = result % (range + 1);
  233|       |
  234|  2.26M|  return static_cast<T>(static_cast<uint64_t>(min) + result);
  235|  2.26M|}
_ZN18FuzzedDataProvider22ConsumeIntegralInRangeImEET_S1_S1_:
  205|  1.25M|T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
  206|  1.25M|  static_assert(std::is_integral_v<T>, "An integral type is required.");
  207|  1.25M|  static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
  208|       |
  209|  1.25M|  if (min > max)
  ------------------
  |  Branch (209:7): [True: 0, False: 1.25M]
  ------------------
  210|      0|    abort();
  211|       |
  212|       |  // Use the biggest type possible to hold the range and the result.
  213|  1.25M|  uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
  214|  1.25M|  uint64_t result = 0;
  215|  1.25M|  size_t offset = 0;
  216|       |
  217|  2.48M|  while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
  ------------------
  |  Branch (217:10): [True: 2.47M, False: 18.0k]
  |  Branch (217:43): [True: 1.36M, False: 1.10M]
  ------------------
  218|  1.36M|         remaining_bytes_ != 0) {
  ------------------
  |  Branch (218:10): [True: 1.23M, False: 123k]
  ------------------
  219|       |    // Pull bytes off the end of the seed data. Experimentally, this seems to
  220|       |    // allow the fuzzer to more easily explore the input space. This makes
  221|       |    // sense, since it works by modifying inputs that caused new code to run,
  222|       |    // and this data is often used to encode length of data read by
  223|       |    // |ConsumeBytes|. Separating out read lengths makes it easier modify the
  224|       |    // contents of the data that is actually read.
  225|  1.23M|    --remaining_bytes_;
  226|  1.23M|    result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
  227|  1.23M|    offset += CHAR_BIT;
  228|  1.23M|  }
  229|       |
  230|       |  // Avoid division by 0, in case |range + 1| results in overflow.
  231|  1.25M|  if (range != std::numeric_limits<decltype(range)>::max())
  ------------------
  |  Branch (231:7): [True: 1.23M, False: 18.0k]
  ------------------
  232|  1.23M|    result = result % (range + 1);
  233|       |
  234|  1.25M|  return static_cast<T>(static_cast<uint64_t>(min) + result);
  235|  1.25M|}
_ZN18FuzzedDataProvider22ConsumeIntegralInRangeIjEET_S1_S1_:
  205|   775k|T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
  206|   775k|  static_assert(std::is_integral_v<T>, "An integral type is required.");
  207|   775k|  static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
  208|       |
  209|   775k|  if (min > max)
  ------------------
  |  Branch (209:7): [True: 0, False: 775k]
  ------------------
  210|      0|    abort();
  211|       |
  212|       |  // Use the biggest type possible to hold the range and the result.
  213|   775k|  uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
  214|   775k|  uint64_t result = 0;
  215|   775k|  size_t offset = 0;
  216|       |
  217|  1.54M|  while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
  ------------------
  |  Branch (217:10): [True: 1.54M, False: 0]
  |  Branch (217:43): [True: 775k, False: 773k]
  ------------------
  218|   775k|         remaining_bytes_ != 0) {
  ------------------
  |  Branch (218:10): [True: 773k, False: 1.68k]
  ------------------
  219|       |    // Pull bytes off the end of the seed data. Experimentally, this seems to
  220|       |    // allow the fuzzer to more easily explore the input space. This makes
  221|       |    // sense, since it works by modifying inputs that caused new code to run,
  222|       |    // and this data is often used to encode length of data read by
  223|       |    // |ConsumeBytes|. Separating out read lengths makes it easier modify the
  224|       |    // contents of the data that is actually read.
  225|   773k|    --remaining_bytes_;
  226|   773k|    result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
  227|   773k|    offset += CHAR_BIT;
  228|   773k|  }
  229|       |
  230|       |  // Avoid division by 0, in case |range + 1| results in overflow.
  231|   775k|  if (range != std::numeric_limits<decltype(range)>::max())
  ------------------
  |  Branch (231:7): [True: 775k, False: 0]
  ------------------
  232|   775k|    result = result % (range + 1);
  233|       |
  234|   775k|  return static_cast<T>(static_cast<uint64_t>(min) + result);
  235|   775k|}
_ZN18FuzzedDataProvider22ConsumeIntegralInRangeIlEET_S1_S1_:
  205|  23.3k|T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
  206|  23.3k|  static_assert(std::is_integral_v<T>, "An integral type is required.");
  207|  23.3k|  static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
  208|       |
  209|  23.3k|  if (min > max)
  ------------------
  |  Branch (209:7): [True: 0, False: 23.3k]
  ------------------
  210|      0|    abort();
  211|       |
  212|       |  // Use the biggest type possible to hold the range and the result.
  213|  23.3k|  uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
  214|  23.3k|  uint64_t result = 0;
  215|  23.3k|  size_t offset = 0;
  216|       |
  217|   181k|  while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
  ------------------
  |  Branch (217:10): [True: 181k, False: 0]
  |  Branch (217:43): [True: 159k, False: 22.2k]
  ------------------
  218|   159k|         remaining_bytes_ != 0) {
  ------------------
  |  Branch (218:10): [True: 158k, False: 1.12k]
  ------------------
  219|       |    // Pull bytes off the end of the seed data. Experimentally, this seems to
  220|       |    // allow the fuzzer to more easily explore the input space. This makes
  221|       |    // sense, since it works by modifying inputs that caused new code to run,
  222|       |    // and this data is often used to encode length of data read by
  223|       |    // |ConsumeBytes|. Separating out read lengths makes it easier modify the
  224|       |    // contents of the data that is actually read.
  225|   158k|    --remaining_bytes_;
  226|   158k|    result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
  227|   158k|    offset += CHAR_BIT;
  228|   158k|  }
  229|       |
  230|       |  // Avoid division by 0, in case |range + 1| results in overflow.
  231|  23.3k|  if (range != std::numeric_limits<decltype(range)>::max())
  ------------------
  |  Branch (231:7): [True: 23.3k, False: 0]
  ------------------
  232|  23.3k|    result = result % (range + 1);
  233|       |
  234|  23.3k|  return static_cast<T>(static_cast<uint64_t>(min) + result);
  235|  23.3k|}
_ZN18FuzzedDataProvider22ConsumeIntegralInRangeIiEET_S1_S1_:
  205|  20.1k|T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
  206|  20.1k|  static_assert(std::is_integral_v<T>, "An integral type is required.");
  207|  20.1k|  static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
  208|       |
  209|  20.1k|  if (min > max)
  ------------------
  |  Branch (209:7): [True: 0, False: 20.1k]
  ------------------
  210|      0|    abort();
  211|       |
  212|       |  // Use the biggest type possible to hold the range and the result.
  213|  20.1k|  uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
  214|  20.1k|  uint64_t result = 0;
  215|  20.1k|  size_t offset = 0;
  216|       |
  217|  77.9k|  while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
  ------------------
  |  Branch (217:10): [True: 77.9k, False: 0]
  |  Branch (217:43): [True: 58.7k, False: 19.2k]
  ------------------
  218|  58.7k|         remaining_bytes_ != 0) {
  ------------------
  |  Branch (218:10): [True: 57.7k, False: 964]
  ------------------
  219|       |    // Pull bytes off the end of the seed data. Experimentally, this seems to
  220|       |    // allow the fuzzer to more easily explore the input space. This makes
  221|       |    // sense, since it works by modifying inputs that caused new code to run,
  222|       |    // and this data is often used to encode length of data read by
  223|       |    // |ConsumeBytes|. Separating out read lengths makes it easier modify the
  224|       |    // contents of the data that is actually read.
  225|  57.7k|    --remaining_bytes_;
  226|  57.7k|    result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
  227|  57.7k|    offset += CHAR_BIT;
  228|  57.7k|  }
  229|       |
  230|       |  // Avoid division by 0, in case |range + 1| results in overflow.
  231|  20.1k|  if (range != std::numeric_limits<decltype(range)>::max())
  ------------------
  |  Branch (231:7): [True: 20.1k, False: 0]
  ------------------
  232|  20.1k|    result = result % (range + 1);
  233|       |
  234|  20.1k|  return static_cast<T>(static_cast<uint64_t>(min) + result);
  235|  20.1k|}

LLVMFuzzerTestOneInput:
  213|  18.0k|{
  214|  18.0k|    test_one_input({data, size});
  215|  18.0k|    return 0;
  216|  18.0k|}
fuzz.cpp:_ZL14test_one_inputNSt3__14spanIKhLm18446744073709551615EEE:
   84|  18.0k|{
   85|  18.0k|    CheckGlobals check{};
   86|  18.0k|    (*Assert(g_test_one_input))(buffer);
  ------------------
  |  |  116|  18.0k|#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
  ------------------
   87|  18.0k|}

_Z19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEE:
  307|  18.0k|{
  308|       |    // This is a big simulation test for TxGraph, which performs a fuzz-derived sequence of valid
  309|       |    // operations on a TxGraph instance, as well as on a simpler (mostly) reimplementation (see
  310|       |    // SimTxGraph above), comparing the outcome of functions that return a result, and finally
  311|       |    // performing a full comparison between the two.
  312|       |
  313|  18.0k|    SeedRandomStateForTest(SeedRand::ZEROS);
  314|  18.0k|    FuzzedDataProvider provider(buffer.data(), buffer.size());
  315|       |
  316|       |    /** Internal test RNG, used only for decisions which would require significant amount of data
  317|       |     *  to be read from the provider, without realistically impacting test sensitivity, and for
  318|       |     *  specialized test cases that are hard to perform more generically. */
  319|  18.0k|    InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
  320|       |
  321|       |    /** Variable used whenever an empty SimTxObject is needed. */
  322|  18.0k|    SimTxObject empty_ref;
  323|       |
  324|       |    /** The maximum number of transactions per (non-oversized) cluster we will use in this
  325|       |     *  simulation. */
  326|  18.0k|    auto max_cluster_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
  327|       |    /** The maximum total size of transactions in a (non-oversized) cluster. */
  328|  18.0k|    auto max_cluster_size = provider.ConsumeIntegralInRange<uint64_t>(1, 0x3fffff * MAX_CLUSTER_COUNT_LIMIT);
  329|       |    /** The amount of work to consider a cluster acceptably linearized. */
  330|  18.0k|    auto acceptable_cost = provider.ConsumeIntegralInRange<uint64_t>(0, 10000);
  331|       |
  332|       |    /** The set of uint64_t "txid"s that have been assigned before. */
  333|  18.0k|    std::set<uint64_t> assigned_txids;
  334|       |
  335|       |    // Construct a real graph, and a vector of simulated graphs (main, and possibly staging).
  336|  18.0k|    auto fallback_order = [&](const TxGraph::Ref& a, const TxGraph::Ref& b) noexcept {
  337|  18.0k|        uint64_t txid_a = static_cast<const SimTxObject&>(a).m_txid;
  338|  18.0k|        uint64_t txid_b = static_cast<const SimTxObject&>(b).m_txid;
  339|  18.0k|        assert(assigned_txids.contains(txid_a));
  340|  18.0k|        assert(assigned_txids.contains(txid_b));
  341|  18.0k|        return txid_a <=> txid_b;
  342|  18.0k|    };
  343|  18.0k|    auto real = MakeTxGraph(
  344|  18.0k|        /*max_cluster_count=*/max_cluster_count,
  345|  18.0k|        /*max_cluster_size=*/max_cluster_size,
  346|  18.0k|        /*acceptable_cost=*/acceptable_cost,
  347|  18.0k|        /*fallback_order=*/fallback_order);
  348|       |
  349|  18.0k|    std::vector<SimTxGraph> sims;
  350|  18.0k|    sims.reserve(2);
  351|  18.0k|    sims.emplace_back(max_cluster_count, max_cluster_size);
  352|       |
  353|       |    /** Struct encapsulating information about a BlockBuilder that's currently live. */
  354|  18.0k|    struct BlockBuilderData
  355|  18.0k|    {
  356|       |        /** BlockBuilder object from real. */
  357|  18.0k|        std::unique_ptr<TxGraph::BlockBuilder> builder;
  358|       |        /** The set of transactions marked as included in *builder. */
  359|  18.0k|        SimTxGraph::SetType included;
  360|       |        /** The set of transactions marked as included or skipped in *builder. */
  361|  18.0k|        SimTxGraph::SetType done;
  362|       |        /** The last chunk feerate returned by *builder. IsEmpty() if none yet. */
  363|  18.0k|        FeePerWeight last_feerate;
  364|       |
  365|  18.0k|        BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
  366|  18.0k|    };
  367|       |
  368|       |    /** Currently active block builders. */
  369|  18.0k|    std::vector<BlockBuilderData> block_builders;
  370|       |
  371|       |    /** Function to pick any SimTxObject (for either sim in sims: from sim.simmap or sim.removed, or the
  372|       |     *  empty one). */
  373|  18.0k|    auto pick_fn = [&]() noexcept -> SimTxObject* {
  374|  18.0k|        size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
  375|       |        /** The number of possible choices. */
  376|  18.0k|        size_t choices = tx_count[0] + sims[0].removed.size() + 1;
  377|  18.0k|        if (sims.size() == 2) {
  378|  18.0k|            tx_count[1] = sims[1].GetTransactionCount();
  379|  18.0k|            choices += tx_count[1] + sims[1].removed.size();
  380|  18.0k|        }
  381|       |        /** Pick one of them. */
  382|  18.0k|        auto choice = provider.ConsumeIntegralInRange<size_t>(0, choices - 1);
  383|       |        // Consider both main and (if it exists) staging.
  384|  18.0k|        for (size_t level = 0; level < sims.size(); ++level) {
  385|  18.0k|            auto& sim = sims[level];
  386|  18.0k|            if (choice < tx_count[level]) {
  387|       |                // Return from graph.
  388|  18.0k|                for (auto i : sim.graph.Positions()) {
  389|  18.0k|                    if (choice == 0) return sim.GetRef(i);
  390|  18.0k|                    --choice;
  391|  18.0k|                }
  392|  18.0k|                assert(false);
  393|  18.0k|            } else {
  394|  18.0k|                choice -= tx_count[level];
  395|  18.0k|            }
  396|  18.0k|            if (choice < sim.removed.size()) {
  397|       |                // Return from removed.
  398|  18.0k|                return sim.removed[choice].get();
  399|  18.0k|            } else {
  400|  18.0k|                choice -= sim.removed.size();
  401|  18.0k|            }
  402|  18.0k|        }
  403|       |        // Return empty.
  404|  18.0k|        assert(choice == 0);
  405|  18.0k|        return &empty_ref;
  406|  18.0k|    };
  407|       |
  408|       |    /** Function to construct the correct fee-size diagram a real graph has based on its graph
  409|       |     *  order (as reported by GetCluster(), so it works for both main and staging). */
  410|  18.0k|    auto get_diagram_fn = [&](TxGraph::Level level_select) -> std::vector<FeeFrac> {
  411|  18.0k|        int level = level_select == TxGraph::Level::MAIN ? 0 : sims.size() - 1;
  412|  18.0k|        auto& sim = sims[level];
  413|       |        // For every transaction in the graph, request its cluster, and throw them into a set.
  414|  18.0k|        std::set<std::vector<TxGraph::Ref*>> clusters;
  415|  18.0k|        for (auto i : sim.graph.Positions()) {
  416|  18.0k|            auto ref = sim.GetRef(i);
  417|  18.0k|            clusters.insert(real->GetCluster(*ref, level_select));
  418|  18.0k|        }
  419|       |        // Compute the chunkings of each (deduplicated) cluster.
  420|  18.0k|        size_t num_tx{0};
  421|  18.0k|        std::vector<FeeFrac> chunk_feerates;
  422|  18.0k|        for (const auto& cluster : clusters) {
  423|  18.0k|            num_tx += cluster.size();
  424|  18.0k|            std::vector<SimTxGraph::Pos> linearization;
  425|  18.0k|            linearization.reserve(cluster.size());
  426|  18.0k|            for (auto refptr : cluster) linearization.push_back(sim.Find(refptr));
  427|  18.0k|            for (const FeeFrac& chunk_feerate : ChunkLinearization(sim.graph, linearization)) {
  428|  18.0k|                chunk_feerates.push_back(chunk_feerate);
  429|  18.0k|            }
  430|  18.0k|        }
  431|       |        // Verify the number of transactions after deduplicating clusters. This implicitly verifies
  432|       |        // that GetCluster on each element of a cluster reports the cluster transactions in the same
  433|       |        // order.
  434|  18.0k|        assert(num_tx == sim.GetTransactionCount());
  435|       |        // Sort by feerate only, since violating topological constraints within same-feerate
  436|       |        // chunks won't affect diagram comparisons.
  437|  18.0k|        std::ranges::sort(chunk_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
  438|  18.0k|        return chunk_feerates;
  439|  18.0k|    };
  440|       |
  441|  1.49M|    LIMITED_WHILE (provider.remaining_bytes() > 0, 200) {
  ------------------
  |  |   23|  1.51M|    for (unsigned _count{limit}; (condition) && _count; --_count)
  |  |  ------------------
  |  |  |  Branch (23:34): [True: 1.49M, False: 16.9k]
  |  |  |  Branch (23:49): [True: 1.49M, False: 1.11k]
  |  |  ------------------
  ------------------
  442|       |        // Read a one-byte command.
  443|  1.49M|        int command = provider.ConsumeIntegral<uint8_t>();
  444|  1.49M|        int orig_command = command;
  445|       |
  446|       |        // Treat the lowest bit of a command as a flag (which selects a variant of some of the
  447|       |        // operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
  448|       |        // the rest of the bits in command.
  449|  1.49M|        bool alt = command & 1;
  450|  1.49M|        TxGraph::Level level_select = (command & 2) ? TxGraph::Level::MAIN : TxGraph::Level::TOP;
  ------------------
  |  Branch (450:39): [True: 494k, False: 1.00M]
  ------------------
  451|  1.49M|        command >>= 2;
  452|       |
  453|       |        /** Use the bottom 2 bits of command to select an entry in the block_builders vector (if
  454|       |         *  any). These use the same bits as alt/level_select, so don't use those in actions below
  455|       |         *  where builder_idx is used as well. */
  456|  1.49M|        int builder_idx = block_builders.empty() ? -1 : int((orig_command & 3) % block_builders.size());
  ------------------
  |  Branch (456:27): [True: 1.44M, False: 51.2k]
  ------------------
  457|       |
  458|       |        // Provide convenient aliases for the top simulated graph (main, or staging if it exists),
  459|       |        // one for the simulated graph selected based on level_select (for operations that can operate
  460|       |        // on both graphs), and one that always refers to the main graph.
  461|  1.49M|        auto& top_sim = sims.back();
  462|  1.49M|        auto& sel_sim = level_select == TxGraph::Level::MAIN ? sims[0] : top_sim;
  ------------------
  |  Branch (462:25): [True: 494k, False: 1.00M]
  ------------------
  463|  1.49M|        auto& main_sim = sims[0];
  464|       |
  465|       |        // Keep decrementing command for each applicable operation, until one is hit. Multiple
  466|       |        // iterations may be necessary.
  467|  1.97M|        while (true) {
  ------------------
  |  Branch (467:16): [True: 1.97M, Folded]
  ------------------
  468|  1.97M|            if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
  ------------------
  |  Branch (468:18): [True: 1.89M, False: 84.6k]
  |  Branch (468:44): [True: 41.9k, False: 42.7k]
  |  Branch (468:64): [True: 1.89M, False: 42.6k]
  |  Branch (468:128): [True: 777k, False: 1.11M]
  ------------------
  469|       |                // AddTransaction.
  470|   777k|                int64_t fee;
  471|   777k|                int32_t size;
  472|   777k|                if (alt) {
  ------------------
  |  Branch (472:21): [True: 20.1k, False: 757k]
  ------------------
  473|       |                    // If alt is true, pick fee and size from the entire range.
  474|  20.1k|                    fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
  475|  20.1k|                    size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
  476|   757k|                } else {
  477|       |                    // Otherwise, use smaller range which consume fewer fuzz input bytes, as just
  478|       |                    // these are likely sufficient to trigger all interesting code paths already.
  479|   757k|                    fee = provider.ConsumeIntegral<uint8_t>();
  480|   757k|                    size = provider.ConsumeIntegralInRange<uint32_t>(1, 0xff);
  481|   757k|                }
  482|   777k|                FeePerWeight feerate{fee, size};
  483|       |                // Pick a novel txid (and not 0, which is reserved for empty_ref).
  484|   777k|                uint64_t txid;
  485|   777k|                do {
  486|   777k|                    txid = rng.rand64();
  487|   777k|                } while (txid == 0 || assigned_txids.contains(txid));
  ------------------
  |  Branch (487:26): [True: 0, False: 777k]
  |  Branch (487:39): [True: 0, False: 777k]
  ------------------
  488|   777k|                assigned_txids.insert(txid);
  489|       |                // Create the transaction in the simulation and the real graph.
  490|   777k|                top_sim.AddTransaction(*real, feerate, txid);
  491|   777k|                break;
  492|  1.20M|            } else if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
  ------------------
  |  Branch (492:25): [True: 1.12M, False: 73.6k]
  |  Branch (492:51): [True: 30.9k, False: 42.7k]
  |  Branch (492:71): [True: 1.08M, False: 71.0k]
  |  Branch (492:133): [True: 125k, False: 961k]
  ------------------
  493|       |                // AddDependency.
  494|   125k|                auto par = pick_fn();
  495|   125k|                auto chl = pick_fn();
  496|   125k|                auto pos_par = top_sim.Find(par);
  497|   125k|                auto pos_chl = top_sim.Find(chl);
  498|   125k|                if (pos_par != SimTxGraph::MISSING && pos_chl != SimTxGraph::MISSING) {
  ------------------
  |  Branch (498:21): [True: 121k, False: 3.56k]
  |  Branch (498:55): [True: 118k, False: 2.62k]
  ------------------
  499|       |                    // Determine if adding this would introduce a cycle (not allowed by TxGraph),
  500|       |                    // and if so, skip.
  501|   118k|                    if (top_sim.graph.Ancestors(pos_par)[pos_chl]) break;
  ------------------
  |  Branch (501:25): [True: 13.5k, False: 105k]
  ------------------
  502|   118k|                }
  503|   111k|                top_sim.AddDependency(par, chl);
  504|   111k|                top_sim.real_is_optimal = false;
  505|   111k|                real->AddDependency(*par, *chl);
  506|   111k|                break;
  507|  1.07M|            } else if ((block_builders.empty() || sims.size() > 1) && top_sim.removed.size() < 100 && command-- == 0) {
  ------------------
  |  Branch (507:25): [True: 1.00M, False: 72.5k]
  |  Branch (507:51): [True: 29.8k, False: 42.7k]
  |  Branch (507:71): [True: 1.02M, False: 2.83k]
  |  Branch (507:103): [True: 41.9k, False: 987k]
  ------------------
  508|       |                // RemoveTransaction. Either all its ancestors or all its descendants are also
  509|       |                // removed (if any), to make sure TxGraph's reordering of removals and dependencies
  510|       |                // has no effect.
  511|  41.9k|                std::vector<TxGraph::Ref*> to_remove;
  512|  41.9k|                to_remove.push_back(pick_fn());
  513|  41.9k|                top_sim.IncludeAncDesc(to_remove, alt);
  514|       |                // The order in which these ancestors/descendants are removed should not matter;
  515|       |                // randomly shuffle them.
  516|  41.9k|                std::shuffle(to_remove.begin(), to_remove.end(), rng);
  517|  47.3k|                for (TxGraph::Ref* ptr : to_remove) {
  ------------------
  |  Branch (517:40): [True: 47.3k, False: 41.9k]
  ------------------
  518|  47.3k|                    real->RemoveTransaction(*ptr);
  519|  47.3k|                    top_sim.RemoveTransaction(ptr);
  520|  47.3k|                }
  521|  41.9k|                break;
  522|  1.03M|            } else if (sel_sim.removed.size() > 0 && command-- == 0) {
  ------------------
  |  Branch (522:24): [True: 471k, False: 562k]
  |  Branch (522:54): [True: 5.62k, False: 465k]
  ------------------
  523|       |                // ~Ref (of an already-removed transaction). Destroying a TxGraph::Ref has an
  524|       |                // observable effect on the TxGraph it refers to, so this simulation permits doing
  525|       |                // so separately from other actions on TxGraph.
  526|       |
  527|       |                // Pick a Ref of sel_sim.removed to destroy. Note that the same Ref may still occur
  528|       |                // in the other graph, and thus not actually trigger ~Ref yet (which is exactly
  529|       |                // what we want, as destroying Refs is only allowed when it does not refer to an
  530|       |                // existing transaction in either graph).
  531|  5.62k|                auto removed_pos = provider.ConsumeIntegralInRange<size_t>(0, sel_sim.removed.size() - 1);
  532|  5.62k|                if (removed_pos != sel_sim.removed.size() - 1) {
  ------------------
  |  Branch (532:21): [True: 2.99k, False: 2.63k]
  ------------------
  533|  2.99k|                    std::swap(sel_sim.removed[removed_pos], sel_sim.removed.back());
  534|  2.99k|                }
  535|  5.62k|                sel_sim.removed.pop_back();
  536|  5.62k|                break;
  537|  1.02M|            } else if (block_builders.empty() && command-- == 0) {
  ------------------
  |  Branch (537:24): [True: 956k, False: 70.9k]
  |  Branch (537:50): [True: 21.9k, False: 934k]
  ------------------
  538|       |                // ~Ref (of any transaction).
  539|  21.9k|                std::vector<TxGraph::Ref*> to_destroy;
  540|  21.9k|                to_destroy.push_back(pick_fn());
  541|  23.9k|                while (true) {
  ------------------
  |  Branch (541:24): [True: 23.9k, Folded]
  ------------------
  542|       |                    // Keep adding either the ancestors or descendants the already picked
  543|       |                    // transactions have in both graphs (main and staging) combined. Destroying
  544|       |                    // will trigger deletions in both, so to have consistent TxGraph behavior, the
  545|       |                    // set must be closed under ancestors, or descendants, in both graphs.
  546|  23.9k|                    auto old_size = to_destroy.size();
  547|  33.0k|                    for (auto& sim : sims) sim.IncludeAncDesc(to_destroy, alt);
  ------------------
  |  Branch (547:36): [True: 33.0k, False: 23.9k]
  ------------------
  548|  23.9k|                    if (to_destroy.size() == old_size) break;
  ------------------
  |  Branch (548:25): [True: 21.9k, False: 1.92k]
  ------------------
  549|  23.9k|                }
  550|       |                // The order in which these ancestors/descendants are destroyed should not matter;
  551|       |                // randomly shuffle them.
  552|  21.9k|                std::shuffle(to_destroy.begin(), to_destroy.end(), rng);
  553|  30.0k|                for (TxGraph::Ref* ptr : to_destroy) {
  ------------------
  |  Branch (553:40): [True: 30.0k, False: 21.9k]
  ------------------
  554|  73.9k|                    for (size_t level = 0; level < sims.size(); ++level) {
  ------------------
  |  Branch (554:44): [True: 43.9k, False: 30.0k]
  ------------------
  555|  43.9k|                        sims[level].DestroyTransaction(ptr, level == sims.size() - 1);
  556|  43.9k|                    }
  557|  30.0k|                }
  558|  21.9k|                break;
  559|  1.00M|            } else if (block_builders.empty() && command-- == 0) {
  ------------------
  |  Branch (559:24): [True: 934k, False: 70.9k]
  |  Branch (559:50): [True: 10.2k, False: 924k]
  ------------------
  560|       |                // SetTransactionFee.
  561|  10.2k|                int64_t fee;
  562|  10.2k|                if (alt) {
  ------------------
  |  Branch (562:21): [True: 3.15k, False: 7.13k]
  ------------------
  563|  3.15k|                    fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
  564|  7.13k|                } else {
  565|  7.13k|                    fee = provider.ConsumeIntegral<uint8_t>();
  566|  7.13k|                }
  567|  10.2k|                auto ref = pick_fn();
  568|  10.2k|                real->SetTransactionFee(*ref, fee);
  569|  12.4k|                for (auto& sim : sims) {
  ------------------
  |  Branch (569:32): [True: 12.4k, False: 10.2k]
  ------------------
  570|  12.4k|                    sim.SetTransactionFee(ref, fee);
  571|  12.4k|                }
  572|  10.2k|                break;
  573|   995k|            } else if (command-- == 0) {
  ------------------
  |  Branch (573:24): [True: 14.2k, False: 981k]
  ------------------
  574|       |                // GetTransactionCount.
  575|  14.2k|                assert(real->GetTransactionCount(level_select) == sel_sim.GetTransactionCount());
  ------------------
  |  Branch (575:17): [True: 14.2k, False: 0]
  ------------------
  576|  14.2k|                break;
  577|   981k|            } else if (command-- == 0) {
  ------------------
  |  Branch (577:24): [True: 8.30k, False: 973k]
  ------------------
  578|       |                // Exists.
  579|  8.30k|                auto ref = pick_fn();
  580|  8.30k|                bool exists = real->Exists(*ref, level_select);
  581|  8.30k|                bool should_exist = sel_sim.Find(ref) != SimTxGraph::MISSING;
  582|  8.30k|                assert(exists == should_exist);
  ------------------
  |  Branch (582:17): [True: 8.30k, False: 0]
  ------------------
  583|  8.30k|                break;
  584|   973k|            } else if (command-- == 0) {
  ------------------
  |  Branch (584:24): [True: 30.1k, False: 942k]
  ------------------
  585|       |                // IsOversized.
  586|  30.1k|                assert(sel_sim.IsOversized() == real->IsOversized(level_select));
  ------------------
  |  Branch (586:17): [True: 30.1k, False: 0]
  ------------------
  587|  30.1k|                break;
  588|   942k|            } else if (command-- == 0) {
  ------------------
  |  Branch (588:24): [True: 12.8k, False: 930k]
  ------------------
  589|       |                // GetIndividualFeerate.
  590|  12.8k|                auto ref = pick_fn();
  591|  12.8k|                auto feerate = real->GetIndividualFeerate(*ref);
  592|  12.8k|                bool found{false};
  593|  16.4k|                for (auto& sim : sims) {
  ------------------
  |  Branch (593:32): [True: 16.4k, False: 12.8k]
  ------------------
  594|  16.4k|                    auto simpos = sim.Find(ref);
  595|  16.4k|                    if (simpos != SimTxGraph::MISSING) {
  ------------------
  |  Branch (595:25): [True: 12.7k, False: 3.69k]
  ------------------
  596|  12.7k|                        found = true;
  597|  12.7k|                        assert(feerate == sim.graph.FeeRate(simpos));
  ------------------
  |  Branch (597:25): [True: 12.7k, False: 0]
  ------------------
  598|  12.7k|                    }
  599|  16.4k|                }
  600|  12.8k|                if (!found) assert(feerate.IsEmpty());
  ------------------
  |  Branch (600:21): [True: 2.25k, False: 10.5k]
  |  Branch (600:29): [True: 2.25k, False: 0]
  ------------------
  601|  12.8k|                break;
  602|   930k|            } else if (!main_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (602:24): [True: 704k, False: 226k]
  |  Branch (602:51): [True: 9.32k, False: 694k]
  ------------------
  603|       |                // GetMainChunkFeerate.
  604|  9.32k|                auto ref = pick_fn();
  605|  9.32k|                auto feerate = real->GetMainChunkFeerate(*ref);
  606|  9.32k|                auto simpos = main_sim.Find(ref);
  607|  9.32k|                if (simpos == SimTxGraph::MISSING) {
  ------------------
  |  Branch (607:21): [True: 1.83k, False: 7.48k]
  ------------------
  608|  1.83k|                    assert(feerate.IsEmpty());
  ------------------
  |  Branch (608:21): [True: 1.83k, False: 0]
  ------------------
  609|  7.48k|                } else {
  610|       |                    // Just do some quick checks that the reported value is in range. A full
  611|       |                    // recomputation of expected chunk feerates is done at the end.
  612|  7.48k|                    assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
  ------------------
  |  Branch (612:21): [True: 7.48k, False: 0]
  ------------------
  613|  7.48k|                    assert(feerate.size <= main_sim.SumAll().size);
  ------------------
  |  Branch (613:21): [True: 7.48k, False: 0]
  ------------------
  614|  7.48k|                }
  615|  9.32k|                break;
  616|   920k|            } else if (!sel_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (616:24): [True: 701k, False: 219k]
  |  Branch (616:50): [True: 10.9k, False: 690k]
  ------------------
  617|       |                // GetAncestors/GetDescendants.
  618|  10.9k|                auto ref = pick_fn();
  619|  10.9k|                auto result = alt ? real->GetDescendants(*ref, level_select)
  ------------------
  |  Branch (619:31): [True: 5.55k, False: 5.42k]
  ------------------
  620|  10.9k|                                  : real->GetAncestors(*ref, level_select);
  621|  10.9k|                assert(result.size() <= max_cluster_count);
  ------------------
  |  Branch (621:17): [True: 10.9k, False: 0]
  ------------------
  622|  10.9k|                auto result_set = sel_sim.MakeSet(result);
  623|  10.9k|                assert(result.size() == result_set.Count());
  ------------------
  |  Branch (623:17): [True: 10.9k, False: 0]
  ------------------
  624|  10.9k|                auto expect_set = sel_sim.GetAncDesc(ref, alt);
  625|  10.9k|                assert(result_set == expect_set);
  ------------------
  |  Branch (625:17): [True: 10.9k, False: 0]
  ------------------
  626|  10.9k|                break;
  627|   909k|            } else if (!sel_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (627:24): [True: 690k, False: 219k]
  |  Branch (627:50): [True: 20.7k, False: 669k]
  ------------------
  628|       |                // GetAncestorsUnion/GetDescendantsUnion.
  629|  20.7k|                std::vector<TxGraph::Ref*> refs;
  630|       |                // Gather a list of up to 15 Ref pointers.
  631|  20.7k|                auto count = provider.ConsumeIntegralInRange<size_t>(0, 15);
  632|  20.7k|                refs.resize(count);
  633|   125k|                for (size_t i = 0; i < count; ++i) {
  ------------------
  |  Branch (633:36): [True: 105k, False: 20.7k]
  ------------------
  634|   105k|                    refs[i] = pick_fn();
  635|   105k|                }
  636|       |                // Their order should not matter, shuffle them.
  637|  20.7k|                std::shuffle(refs.begin(), refs.end(), rng);
  638|       |                // Invoke the real function, and convert to SimPos set.
  639|  20.7k|                auto result = alt ? real->GetDescendantsUnion(refs, level_select)
  ------------------
  |  Branch (639:31): [True: 10.2k, False: 10.4k]
  ------------------
  640|  20.7k|                                  : real->GetAncestorsUnion(refs, level_select);
  641|  20.7k|                auto result_set = sel_sim.MakeSet(result);
  642|  20.7k|                assert(result.size() == result_set.Count());
  ------------------
  |  Branch (642:17): [True: 20.7k, False: 0]
  ------------------
  643|       |                // Compute the expected result.
  644|  20.7k|                SimTxGraph::SetType expect_set;
  645|   105k|                for (TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
  ------------------
  |  Branch (645:40): [True: 105k, False: 20.7k]
  ------------------
  646|       |                // Compare.
  647|  20.7k|                assert(result_set == expect_set);
  ------------------
  |  Branch (647:17): [True: 20.7k, False: 0]
  ------------------
  648|  20.7k|                break;
  649|   889k|            } else if (!sel_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (649:24): [True: 669k, False: 219k]
  |  Branch (649:50): [True: 12.4k, False: 657k]
  ------------------
  650|       |                // GetCluster.
  651|  12.4k|                auto ref = pick_fn();
  652|  12.4k|                auto result = real->GetCluster(*ref, level_select);
  653|       |                // Check cluster count limit.
  654|  12.4k|                assert(result.size() <= max_cluster_count);
  ------------------
  |  Branch (654:17): [True: 12.4k, False: 0]
  ------------------
  655|       |                // Require the result to be topologically valid and not contain duplicates.
  656|  12.4k|                auto left = sel_sim.graph.Positions();
  657|  12.4k|                uint64_t total_size{0};
  658|  84.4k|                for (auto refptr : result) {
  ------------------
  |  Branch (658:34): [True: 84.4k, False: 12.4k]
  ------------------
  659|  84.4k|                    auto simpos = sel_sim.Find(refptr);
  660|  84.4k|                    total_size += sel_sim.graph.FeeRate(simpos).size;
  661|  84.4k|                    assert(simpos != SimTxGraph::MISSING);
  ------------------
  |  Branch (661:21): [True: 84.4k, False: 0]
  ------------------
  662|  84.4k|                    assert(left[simpos]);
  ------------------
  |  Branch (662:21): [True: 84.4k, False: 0]
  ------------------
  663|  84.4k|                    left.Reset(simpos);
  664|  84.4k|                    assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
  ------------------
  |  Branch (664:21): [True: 84.4k, False: 0]
  ------------------
  665|  84.4k|                }
  666|       |                // Check cluster size limit.
  667|  12.4k|                assert(total_size <= max_cluster_size);
  ------------------
  |  Branch (667:17): [True: 12.4k, False: 0]
  ------------------
  668|       |                // Require the set to be connected.
  669|  12.4k|                auto result_set = sel_sim.MakeSet(result);
  670|  12.4k|                assert(sel_sim.graph.IsConnected(result_set));
  ------------------
  |  Branch (670:17): [True: 12.4k, False: 0]
  ------------------
  671|       |                // If ref exists, the result must contain it. If not, it must be empty.
  672|  12.4k|                auto simpos = sel_sim.Find(ref);
  673|  12.4k|                if (simpos != SimTxGraph::MISSING) {
  ------------------
  |  Branch (673:21): [True: 9.14k, False: 3.32k]
  ------------------
  674|  9.14k|                    assert(result_set[simpos]);
  ------------------
  |  Branch (674:21): [True: 9.14k, False: 0]
  ------------------
  675|  9.14k|                } else {
  676|  3.32k|                    assert(result_set.None());
  ------------------
  |  Branch (676:21): [True: 3.32k, False: 0]
  ------------------
  677|  3.32k|                }
  678|       |                // Require the set not to have ancestors or descendants outside of it.
  679|  84.4k|                for (auto i : result_set) {
  ------------------
  |  Branch (679:29): [True: 84.4k, False: 12.4k]
  ------------------
  680|  84.4k|                    assert(sel_sim.graph.Ancestors(i).IsSubsetOf(result_set));
  ------------------
  |  Branch (680:21): [True: 84.4k, False: 0]
  ------------------
  681|  84.4k|                    assert(sel_sim.graph.Descendants(i).IsSubsetOf(result_set));
  ------------------
  |  Branch (681:21): [True: 84.4k, False: 0]
  ------------------
  682|  84.4k|                }
  683|  12.4k|                break;
  684|   876k|            } else if (command-- == 0) {
  ------------------
  |  Branch (684:24): [True: 13.6k, False: 863k]
  ------------------
  685|       |                // HaveStaging.
  686|  13.6k|                assert((sims.size() == 2) == real->HaveStaging());
  ------------------
  |  Branch (686:17): [True: 13.6k, False: 0]
  ------------------
  687|  13.6k|                break;
  688|   863k|            } else if (sims.size() < 2 && command-- == 0) {
  ------------------
  |  Branch (688:24): [True: 553k, False: 309k]
  |  Branch (688:43): [True: 58.7k, False: 494k]
  ------------------
  689|       |                // StartStaging.
  690|  58.7k|                sims.emplace_back(sims.back());
  691|  58.7k|                sims.back().modified = SimTxGraph::SetType{};
  692|  58.7k|                real->StartStaging();
  693|  58.7k|                break;
  694|   804k|            } else if (block_builders.empty() && sims.size() > 1 && command-- == 0) {
  ------------------
  |  Branch (694:24): [True: 745k, False: 59.0k]
  |  Branch (694:50): [True: 282k, False: 462k]
  |  Branch (694:69): [True: 4.35k, False: 278k]
  ------------------
  695|       |                // CommitStaging.
  696|  4.35k|                real->CommitStaging();
  697|       |                // Resulting main level is only guaranteed to be optimal if all levels are
  698|  4.35k|                const bool main_optimal = std::all_of(sims.cbegin(), sims.cend(), [](const auto &sim) { return sim.real_is_optimal; });
  699|  4.35k|                sims.erase(sims.begin());
  700|  4.35k|                sims.front().real_is_optimal = main_optimal;
  701|  4.35k|                break;
  702|   799k|            } else if (sims.size() > 1 && command-- == 0) {
  ------------------
  |  Branch (702:24): [True: 305k, False: 494k]
  |  Branch (702:43): [True: 46.9k, False: 258k]
  ------------------
  703|       |                // AbortStaging.
  704|  46.9k|                real->AbortStaging();
  705|  46.9k|                sims.pop_back();
  706|       |                // Reset the cached oversized value (if TxGraph::Ref destructions triggered
  707|       |                // removals of main transactions while staging was active, then aborting will
  708|       |                // cause it to be re-evaluated in TxGraph).
  709|  46.9k|                sims.back().oversized = std::nullopt;
  710|  46.9k|                break;
  711|   753k|            } else if (!main_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (711:24): [True: 573k, False: 179k]
  |  Branch (711:51): [True: 8.10k, False: 565k]
  ------------------
  712|       |                // CompareMainOrder.
  713|  8.10k|                auto ref_a = pick_fn();
  714|  8.10k|                auto ref_b = pick_fn();
  715|  8.10k|                auto sim_a = main_sim.Find(ref_a);
  716|  8.10k|                auto sim_b = main_sim.Find(ref_b);
  717|       |                // Both transactions must exist in the main graph.
  718|  8.10k|                if (sim_a == SimTxGraph::MISSING || sim_b == SimTxGraph::MISSING) break;
  ------------------
  |  Branch (718:21): [True: 1.42k, False: 6.67k]
  |  Branch (718:53): [True: 594, False: 6.08k]
  ------------------
  719|  6.08k|                auto cmp = real->CompareMainOrder(*ref_a, *ref_b);
  720|       |                // Distinct transactions have distinct places.
  721|  6.08k|                if (sim_a != sim_b) assert(cmp != 0);
  ------------------
  |  Branch (721:21): [True: 3.71k, False: 2.36k]
  |  Branch (721:37): [True: 3.71k, False: 0]
  ------------------
  722|       |                // Ancestors go before descendants.
  723|  6.08k|                if (main_sim.graph.Ancestors(sim_a)[sim_b]) assert(cmp >= 0);
  ------------------
  |  Branch (723:21): [True: 2.47k, False: 3.60k]
  |  Branch (723:61): [True: 2.47k, False: 0]
  ------------------
  724|  6.08k|                if (main_sim.graph.Descendants(sim_a)[sim_b]) assert(cmp <= 0);
  ------------------
  |  Branch (724:21): [True: 2.41k, False: 3.66k]
  |  Branch (724:63): [True: 2.41k, False: 0]
  ------------------
  725|       |                // Do not verify consistency with chunk feerates, as we cannot easily determine
  726|       |                // these here without making more calls to real, which could affect its internal
  727|       |                // state. A full comparison is done at the end.
  728|  6.08k|                break;
  729|   744k|            } else if (!sel_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (729:24): [True: 569k, False: 175k]
  |  Branch (729:50): [True: 17.2k, False: 551k]
  ------------------
  730|       |                // CountDistinctClusters.
  731|  17.2k|                std::vector<TxGraph::Ref*> refs;
  732|       |                // Gather a list of up to 15 (or up to 255) Ref pointers.
  733|  17.2k|                auto count = provider.ConsumeIntegralInRange<size_t>(0, alt ? 255 : 15);
  ------------------
  |  Branch (733:73): [True: 6.01k, False: 11.1k]
  ------------------
  734|  17.2k|                refs.resize(count);
  735|   648k|                for (size_t i = 0; i < count; ++i) {
  ------------------
  |  Branch (735:36): [True: 631k, False: 17.2k]
  ------------------
  736|   631k|                    refs[i] = pick_fn();
  737|   631k|                }
  738|       |                // Their order should not matter, shuffle them.
  739|  17.2k|                std::shuffle(refs.begin(), refs.end(), rng);
  740|       |                // Invoke the real function.
  741|  17.2k|                auto result = real->CountDistinctClusters(refs, level_select);
  742|       |                // Build a set with representatives of the clusters the Refs occur in the
  743|       |                // simulated graph. For each, remember the lowest-index transaction SimPos in the
  744|       |                // cluster.
  745|  17.2k|                SimTxGraph::SetType sim_reps;
  746|   631k|                for (auto ref : refs) {
  ------------------
  |  Branch (746:31): [True: 631k, False: 17.2k]
  ------------------
  747|       |                    // Skip Refs that do not occur in the simulated graph.
  748|   631k|                    auto simpos = sel_sim.Find(ref);
  749|   631k|                    if (simpos == SimTxGraph::MISSING) continue;
  ------------------
  |  Branch (749:25): [True: 105k, False: 526k]
  ------------------
  750|       |                    // Find the component that includes ref.
  751|   526k|                    auto component = sel_sim.graph.GetConnectedComponent(sel_sim.graph.Positions(), simpos);
  752|       |                    // Remember the lowest-index SimPos in component, as a representative for it.
  753|   526k|                    assert(component.Any());
  ------------------
  |  Branch (753:21): [True: 526k, False: 0]
  ------------------
  754|   526k|                    sim_reps.Set(component.First());
  755|   526k|                }
  756|       |                // Compare the number of deduplicated representatives with the value returned by
  757|       |                // the real function.
  758|  17.2k|                assert(result == sim_reps.Count());
  ------------------
  |  Branch (758:17): [True: 17.2k, False: 0]
  ------------------
  759|  17.2k|                break;
  760|   727k|            } else if (command-- == 0) {
  ------------------
  |  Branch (760:24): [True: 22.1k, False: 705k]
  ------------------
  761|       |                // DoWork.
  762|  22.1k|                uint64_t max_cost = provider.ConsumeIntegralInRange<uint64_t>(0, alt ? 10000 : 255);
  ------------------
  |  Branch (762:82): [True: 12.2k, False: 9.84k]
  ------------------
  763|  22.1k|                bool ret = real->DoWork(max_cost);
  764|  22.1k|                uint64_t cost_for_optimal{0};
  765|  50.0k|                for (unsigned level = 0; level < sims.size(); ++level) {
  ------------------
  |  Branch (765:42): [True: 27.9k, False: 22.1k]
  ------------------
  766|       |                    // DoWork() will not optimize oversized levels, or the main level if a builder
  767|       |                    // is present. Note that this impacts the DoWork() return value, as true means
  768|       |                    // that non-optimal clusters may remain within such oversized or builder-having
  769|       |                    // levels.
  770|  27.9k|                    if (sims[level].IsOversized()) continue;
  ------------------
  |  Branch (770:25): [True: 4.76k, False: 23.1k]
  ------------------
  771|  23.1k|                    if (level == 0 && !block_builders.empty()) continue;
  ------------------
  |  Branch (771:25): [True: 18.3k, False: 4.77k]
  |  Branch (771:39): [True: 2.16k, False: 16.2k]
  ------------------
  772|       |                    // If neither of the two above conditions holds, and DoWork() returned true,
  773|       |                    // then the level is optimal.
  774|  20.9k|                    if (ret) {
  ------------------
  |  Branch (774:25): [True: 12.0k, False: 8.96k]
  ------------------
  775|  12.0k|                        sims[level].real_is_optimal = true;
  776|  12.0k|                    }
  777|       |                    // Compute how much work would be needed to make everything optimal.
  778|   457k|                    for (auto component : sims[level].GetComponents()) {
  ------------------
  |  Branch (778:41): [True: 457k, False: 20.9k]
  ------------------
  779|   457k|                        auto cost_opt_this_cluster = MaxOptimalLinearizationCost(component.Count());
  780|   457k|                        if (cost_opt_this_cluster > acceptable_cost) {
  ------------------
  |  Branch (780:29): [True: 19.3k, False: 437k]
  ------------------
  781|       |                            // If the amount of work required to linearize this cluster
  782|       |                            // optimally exceeds acceptable_cost, DoWork() may process it in two
  783|       |                            // stages: once to acceptable, and once to optimal.
  784|  19.3k|                            cost_for_optimal += cost_opt_this_cluster + acceptable_cost;
  785|   437k|                        } else {
  786|   437k|                            cost_for_optimal += cost_opt_this_cluster;
  787|   437k|                        }
  788|   457k|                    }
  789|  20.9k|                }
  790|  22.1k|                if (!ret) {
  ------------------
  |  Branch (790:21): [True: 7.16k, False: 14.9k]
  ------------------
  791|       |                    // DoWork can only have more work left if the requested amount of work
  792|       |                    // was insufficient to linearize everything optimally within the levels it is
  793|       |                    // allowed to touch.
  794|  7.16k|                    assert(max_cost <= cost_for_optimal);
  ------------------
  |  Branch (794:21): [True: 7.16k, False: 0]
  ------------------
  795|  7.16k|                }
  796|  22.1k|                break;
  797|   705k|            } else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) {
  ------------------
  |  Branch (797:24): [True: 248k, False: 457k]
  |  Branch (797:44): [True: 158k, False: 89.9k]
  |  Branch (797:70): [True: 154k, False: 3.32k]
  |  Branch (797:96): [True: 37.9k, False: 116k]
  ------------------
  798|       |                // GetMainStagingDiagrams()
  799|  37.9k|                auto [real_main_diagram, real_staged_diagram] = real->GetMainStagingDiagrams();
  800|  37.9k|                auto real_sum_main = std::accumulate(real_main_diagram.begin(), real_main_diagram.end(), FeeFrac{});
  801|  37.9k|                auto real_sum_staged = std::accumulate(real_staged_diagram.begin(), real_staged_diagram.end(), FeeFrac{});
  802|  37.9k|                auto real_gain = real_sum_staged - real_sum_main;
  803|  37.9k|                auto sim_gain = sims[1].SumAll() - sims[0].SumAll();
  804|       |                // Just check that the total fee gained/lost and size gained/lost according to the
  805|       |                // diagram matches the difference in these values in the simulated graph. A more
  806|       |                // complete check of the GetMainStagingDiagrams result is performed at the end.
  807|  37.9k|                assert(sim_gain == real_gain);
  ------------------
  |  Branch (807:17): [True: 37.9k, False: 0]
  ------------------
  808|       |                // Check that the feerates in each diagram are monotonically decreasing.
  809|  1.10M|                for (size_t i = 1; i < real_main_diagram.size(); ++i) {
  ------------------
  |  Branch (809:36): [True: 1.06M, False: 37.9k]
  ------------------
  810|  1.06M|                    assert(ByRatio{real_main_diagram[i]} <= ByRatio{real_main_diagram[i - 1]});
  ------------------
  |  Branch (810:21): [True: 1.06M, False: 0]
  ------------------
  811|  1.06M|                }
  812|   740k|                for (size_t i = 1; i < real_staged_diagram.size(); ++i) {
  ------------------
  |  Branch (812:36): [True: 702k, False: 37.9k]
  ------------------
  813|   702k|                    assert(ByRatio{real_staged_diagram[i]} <= ByRatio{real_staged_diagram[i - 1]});
  ------------------
  |  Branch (813:21): [True: 702k, False: 0]
  ------------------
  814|   702k|                }
  815|  37.9k|                break;
  816|   667k|            } else if (block_builders.size() < 4 && !main_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (816:24): [True: 650k, False: 17.3k]
  |  Branch (816:53): [True: 474k, False: 175k]
  |  Branch (816:80): [True: 4.36k, False: 469k]
  ------------------
  817|       |                // GetBlockBuilder.
  818|  4.36k|                block_builders.emplace_back(real->GetBlockBuilder());
  819|  4.36k|                break;
  820|   663k|            } else if (!block_builders.empty() && command-- == 0) {
  ------------------
  |  Branch (820:24): [True: 47.2k, False: 615k]
  |  Branch (820:51): [True: 1.87k, False: 45.3k]
  ------------------
  821|       |                // ~BlockBuilder.
  822|  1.87k|                block_builders.erase(block_builders.begin() + builder_idx);
  823|  1.87k|                break;
  824|   661k|            } else if (!block_builders.empty() && command-- == 0) {
  ------------------
  |  Branch (824:24): [True: 45.3k, False: 615k]
  |  Branch (824:51): [True: 7.07k, False: 38.3k]
  ------------------
  825|       |                // BlockBuilder::GetCurrentChunk, followed by Include/Skip.
  826|  7.07k|                auto& builder_data = block_builders[builder_idx];
  827|  7.07k|                auto new_included = builder_data.included;
  828|  7.07k|                auto new_done = builder_data.done;
  829|  7.07k|                auto chunk = builder_data.builder->GetCurrentChunk();
  830|  7.07k|                if (chunk) {
  ------------------
  |  Branch (830:21): [True: 5.47k, False: 1.60k]
  ------------------
  831|       |                    // Chunk feerates must be monotonously decreasing.
  832|  5.47k|                    if (!builder_data.last_feerate.IsEmpty()) {
  ------------------
  |  Branch (832:25): [True: 4.29k, False: 1.17k]
  ------------------
  833|  4.29k|                        assert(ByRatio{chunk->second} <= ByRatio{builder_data.last_feerate});
  ------------------
  |  Branch (833:25): [True: 4.29k, False: 0]
  ------------------
  834|  4.29k|                    }
  835|  5.47k|                    builder_data.last_feerate = chunk->second;
  836|       |                    // Verify the contents of GetCurrentChunk.
  837|  5.47k|                    FeePerWeight sum_feerate;
  838|  6.47k|                    for (TxGraph::Ref* ref : chunk->first) {
  ------------------
  |  Branch (838:44): [True: 6.47k, False: 5.47k]
  ------------------
  839|       |                        // Each transaction in the chunk must exist in the main graph.
  840|  6.47k|                        auto simpos = main_sim.Find(ref);
  841|  6.47k|                        assert(simpos != SimTxGraph::MISSING);
  ------------------
  |  Branch (841:25): [True: 6.47k, False: 0]
  ------------------
  842|       |                        // Verify the claimed chunk feerate.
  843|  6.47k|                        sum_feerate += main_sim.graph.FeeRate(simpos);
  844|       |                        // Make sure no transaction is reported twice.
  845|  6.47k|                        assert(!new_done[simpos]);
  ------------------
  |  Branch (845:25): [True: 6.47k, False: 0]
  ------------------
  846|  6.47k|                        new_done.Set(simpos);
  847|       |                        // The concatenation of all included transactions must be topologically valid.
  848|  6.47k|                        new_included.Set(simpos);
  849|  6.47k|                        assert(main_sim.graph.Ancestors(simpos).IsSubsetOf(new_included));
  ------------------
  |  Branch (849:25): [True: 6.47k, False: 0]
  ------------------
  850|  6.47k|                    }
  851|  5.47k|                    assert(sum_feerate == chunk->second);
  ------------------
  |  Branch (851:21): [True: 5.47k, False: 0]
  ------------------
  852|  5.47k|                } else {
  853|       |                    // When we reach the end, if nothing was skipped, the entire graph should have
  854|       |                    // been reported.
  855|  1.60k|                    if (builder_data.done == builder_data.included) {
  ------------------
  |  Branch (855:25): [True: 553, False: 1.04k]
  ------------------
  856|    553|                        assert(builder_data.done.Count() == main_sim.GetTransactionCount());
  ------------------
  |  Branch (856:25): [True: 553, False: 0]
  ------------------
  857|    553|                    }
  858|  1.60k|                }
  859|       |                // Possibly invoke GetCurrentChunk() again, which should give the same result.
  860|  7.07k|                if ((orig_command % 7) >= 5) {
  ------------------
  |  Branch (860:21): [True: 1.04k, False: 6.02k]
  ------------------
  861|  1.04k|                    auto chunk2 = builder_data.builder->GetCurrentChunk();
  862|  1.04k|                    assert(chunk == chunk2);
  ------------------
  |  Branch (862:21): [True: 1.04k, False: 0]
  ------------------
  863|  1.04k|                }
  864|       |                // Skip or include.
  865|  7.07k|                if ((orig_command % 5) >= 3) {
  ------------------
  |  Branch (865:21): [True: 5.87k, False: 1.20k]
  ------------------
  866|       |                    // Skip.
  867|  5.87k|                    builder_data.builder->Skip();
  868|  5.87k|                } else {
  869|       |                    // Include.
  870|  1.20k|                    builder_data.builder->Include();
  871|  1.20k|                    builder_data.included = new_included;
  872|  1.20k|                }
  873|  7.07k|                builder_data.done = new_done;
  874|  7.07k|                break;
  875|   654k|            } else if (!main_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (875:24): [True: 478k, False: 175k]
  |  Branch (875:51): [True: 19.1k, False: 459k]
  ------------------
  876|       |                // GetWorstMainChunk.
  877|  19.1k|                auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
  878|       |                // Just do some sanity checks here. Consistency with GetBlockBuilder is checked
  879|       |                // below.
  880|  19.1k|                if (main_sim.GetTransactionCount() == 0) {
  ------------------
  |  Branch (880:21): [True: 1.50k, False: 17.6k]
  ------------------
  881|  1.50k|                    assert(worst_chunk.empty());
  ------------------
  |  Branch (881:21): [True: 1.50k, False: 0]
  ------------------
  882|  1.50k|                    assert(worst_chunk_feerate.IsEmpty());
  ------------------
  |  Branch (882:21): [True: 1.50k, False: 0]
  ------------------
  883|  17.6k|                } else {
  884|  17.6k|                    assert(!worst_chunk.empty());
  ------------------
  |  Branch (884:21): [True: 17.6k, False: 0]
  ------------------
  885|  17.6k|                    SimTxGraph::SetType done;
  886|  17.6k|                    FeePerWeight sum;
  887|  29.3k|                    for (TxGraph::Ref* ref : worst_chunk) {
  ------------------
  |  Branch (887:44): [True: 29.3k, False: 17.6k]
  ------------------
  888|       |                        // Each transaction in the chunk must exist in the main graph.
  889|  29.3k|                        auto simpos = main_sim.Find(ref);
  890|  29.3k|                        assert(simpos != SimTxGraph::MISSING);
  ------------------
  |  Branch (890:25): [True: 29.3k, False: 0]
  ------------------
  891|  29.3k|                        sum += main_sim.graph.FeeRate(simpos);
  892|       |                        // Make sure the chunk contains no duplicate transactions.
  893|  29.3k|                        assert(!done[simpos]);
  ------------------
  |  Branch (893:25): [True: 29.3k, False: 0]
  ------------------
  894|  29.3k|                        done.Set(simpos);
  895|       |                        // All elements are preceded by all their descendants.
  896|  29.3k|                        assert(main_sim.graph.Descendants(simpos).IsSubsetOf(done));
  ------------------
  |  Branch (896:25): [True: 29.3k, False: 0]
  ------------------
  897|  29.3k|                    }
  898|  17.6k|                    assert(sum == worst_chunk_feerate);
  ------------------
  |  Branch (898:21): [True: 17.6k, False: 0]
  ------------------
  899|  17.6k|                }
  900|  19.1k|                break;
  901|   635k|            } else if ((block_builders.empty() || sims.size() > 1) && command-- == 0) {
  ------------------
  |  Branch (901:25): [True: 598k, False: 36.6k]
  |  Branch (901:51): [True: 17.1k, False: 19.4k]
  |  Branch (901:71): [True: 38.6k, False: 576k]
  ------------------
  902|       |                // Trim.
  903|  38.6k|                bool was_oversized = top_sim.IsOversized();
  904|  38.6k|                auto removed = real->Trim();
  905|       |                // Verify that something was removed if and only if there was an oversized cluster.
  906|  38.6k|                assert(was_oversized == !removed.empty());
  ------------------
  |  Branch (906:17): [True: 38.6k, False: 0]
  ------------------
  907|  38.6k|                if (!was_oversized) break;
  ------------------
  |  Branch (907:21): [True: 17.0k, False: 21.6k]
  ------------------
  908|  21.6k|                auto removed_set = top_sim.MakeSet(removed);
  909|       |                // The removed set must contain all its own descendants.
  910|   194k|                for (auto simpos : removed_set) {
  ------------------
  |  Branch (910:34): [True: 194k, False: 21.6k]
  ------------------
  911|   194k|                    assert(top_sim.graph.Descendants(simpos).IsSubsetOf(removed_set));
  ------------------
  |  Branch (911:21): [True: 194k, False: 0]
  ------------------
  912|   194k|                }
  913|       |                // Something from every oversized cluster should have been removed, and nothing
  914|       |                // else.
  915|  21.6k|                assert(top_sim.MatchesOversizedClusters(removed_set));
  ------------------
  |  Branch (915:17): [True: 21.6k, False: 0]
  ------------------
  916|       |
  917|       |                // Apply all removals to the simulation, and verify the result is no longer
  918|       |                // oversized. Don't query the real graph for oversizedness; it is compared
  919|       |                // against the simulation anyway later.
  920|   194k|                for (auto simpos : removed_set) {
  ------------------
  |  Branch (920:34): [True: 194k, False: 21.6k]
  ------------------
  921|   194k|                    top_sim.RemoveTransaction(top_sim.GetRef(simpos));
  922|   194k|                }
  923|  21.6k|                assert(!top_sim.IsOversized());
  ------------------
  |  Branch (923:17): [True: 21.6k, False: 0]
  ------------------
  924|  21.6k|                break;
  925|   596k|            } else if ((block_builders.empty() || sims.size() > 1) &&
  ------------------
  |  Branch (925:25): [True: 560k, False: 35.9k]
  |  Branch (925:51): [True: 16.4k, False: 19.4k]
  ------------------
  926|   576k|                       top_sim.GetTransactionCount() > max_cluster_count && !top_sim.IsOversized() && command-- == 0) {
  ------------------
  |  Branch (926:24): [True: 338k, False: 238k]
  |  Branch (926:77): [True: 234k, False: 103k]
  |  Branch (926:103): [True: 92.4k, False: 141k]
  ------------------
  927|       |                // Trim (special case which avoids apparent cycles in the implicit approximate
  928|       |                // dependency graph constructed inside the Trim() implementation). This is worth
  929|       |                // testing separately, because such cycles cannot occur in realistic scenarios,
  930|       |                // but this is hard to replicate in general in this fuzz test.
  931|       |
  932|       |                // First, we need to have dependencies applied and linearizations fixed to avoid
  933|       |                // circular dependencies in implied graph; trigger it via whatever means.
  934|  92.4k|                real->CountDistinctClusters({}, TxGraph::Level::TOP);
  935|       |
  936|       |                // Gather the current clusters.
  937|  92.4k|                auto clusters = top_sim.GetComponents();
  938|       |
  939|       |                // Merge clusters randomly until at least one oversized one appears.
  940|  92.4k|                bool made_oversized = false;
  941|  92.4k|                auto merges_left = clusters.size() - 1;
  942|  1.87M|                while (merges_left > 0) {
  ------------------
  |  Branch (942:24): [True: 1.78M, False: 92.4k]
  ------------------
  943|  1.78M|                    --merges_left;
  944|       |                    // Find positions of clusters in the clusters vector to merge together.
  945|  1.78M|                    auto par_cl = rng.randrange(clusters.size());
  946|  1.78M|                    auto chl_cl = rng.randrange(clusters.size() - 1);
  947|  1.78M|                    chl_cl += (chl_cl >= par_cl);
  948|  1.78M|                    Assume(chl_cl != par_cl);
  ------------------
  |  |  128|  1.78M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  949|       |                    // Add between 1 and 3 dependencies between them. As all are in the same
  950|       |                    // direction (from the child cluster to parent cluster), no cycles are possible,
  951|       |                    // regardless of what internal topology Trim() uses as approximation within the
  952|       |                    // clusters.
  953|  1.78M|                    int num_deps = rng.randrange(3) + 1;
  954|  5.35M|                    for (int i = 0; i < num_deps; ++i) {
  ------------------
  |  Branch (954:37): [True: 3.57M, False: 1.78M]
  ------------------
  955|       |                        // Find a parent transaction in the parent cluster.
  956|  3.57M|                        auto par_idx = rng.randrange(clusters[par_cl].Count());
  957|  3.57M|                        SimTxGraph::Pos par_pos = 0;
  958|  8.43M|                        for (auto j : clusters[par_cl]) {
  ------------------
  |  Branch (958:37): [True: 8.43M, False: 0]
  ------------------
  959|  8.43M|                            if (par_idx == 0) {
  ------------------
  |  Branch (959:33): [True: 3.57M, False: 4.86M]
  ------------------
  960|  3.57M|                                par_pos = j;
  961|  3.57M|                                break;
  962|  3.57M|                            }
  963|  4.86M|                            --par_idx;
  964|  4.86M|                        }
  965|       |                        // Find a child transaction in the child cluster.
  966|  3.57M|                        auto chl_idx = rng.randrange(clusters[chl_cl].Count());
  967|  3.57M|                        SimTxGraph::Pos chl_pos = 0;
  968|  8.45M|                        for (auto j : clusters[chl_cl]) {
  ------------------
  |  Branch (968:37): [True: 8.45M, False: 0]
  ------------------
  969|  8.45M|                            if (chl_idx == 0) {
  ------------------
  |  Branch (969:33): [True: 3.57M, False: 4.87M]
  ------------------
  970|  3.57M|                                chl_pos = j;
  971|  3.57M|                                break;
  972|  3.57M|                            }
  973|  4.87M|                            --chl_idx;
  974|  4.87M|                        }
  975|       |                        // Add dependency to both simulation and real TxGraph.
  976|  3.57M|                        auto par_ref = top_sim.GetRef(par_pos);
  977|  3.57M|                        auto chl_ref = top_sim.GetRef(chl_pos);
  978|  3.57M|                        top_sim.AddDependency(par_ref, chl_ref);
  979|  3.57M|                        real->AddDependency(*par_ref, *chl_ref);
  980|  3.57M|                    }
  981|       |                    // Compute the combined cluster.
  982|  1.78M|                    auto par_cluster = clusters[par_cl];
  983|  1.78M|                    auto chl_cluster = clusters[chl_cl];
  984|  1.78M|                    auto new_cluster = par_cluster | chl_cluster;
  985|       |                    // Remove the parent and child cluster from clusters.
  986|  1.78M|                    std::erase_if(clusters, [&](const auto& cl) noexcept { return cl == par_cluster || cl == chl_cluster; });
  987|       |                    // Add the combined cluster.
  988|  1.78M|                    clusters.push_back(new_cluster);
  989|       |                    // If this is the first merge that causes an oversized cluster to appear, pick
  990|       |                    // a random number of further merges to appear.
  991|  1.78M|                    if (!made_oversized) {
  ------------------
  |  Branch (991:25): [True: 1.47M, False: 310k]
  ------------------
  992|  1.47M|                        made_oversized = new_cluster.Count() > max_cluster_count;
  993|  1.47M|                        if (!made_oversized) {
  ------------------
  |  Branch (993:29): [True: 1.38M, False: 91.2k]
  ------------------
  994|  1.38M|                            FeeFrac total;
  995|  8.02M|                            for (auto i : new_cluster) total += top_sim.graph.FeeRate(i);
  ------------------
  |  Branch (995:41): [True: 8.02M, False: 1.38M]
  ------------------
  996|  1.38M|                            if (uint32_t(total.size) > max_cluster_size) made_oversized = true;
  ------------------
  |  Branch (996:33): [True: 1.19k, False: 1.38M]
  ------------------
  997|  1.38M|                        }
  998|  1.47M|                        if (made_oversized) merges_left = rng.randrange(clusters.size());
  ------------------
  |  Branch (998:29): [True: 92.4k, False: 1.38M]
  ------------------
  999|  1.47M|                    }
 1000|  1.78M|                }
 1001|       |
 1002|       |                // Determine an upper bound on how many transactions are removed.
 1003|  92.4k|                uint32_t max_removed = 0;
 1004|   402k|                for (auto& cluster : clusters) {
  ------------------
  |  Branch (1004:36): [True: 402k, False: 92.4k]
  ------------------
 1005|       |                    // Gather all transaction sizes in the to-be-combined cluster.
 1006|   402k|                    std::vector<uint32_t> sizes;
 1007|  3.69M|                    for (auto i : cluster) sizes.push_back(top_sim.graph.FeeRate(i).size);
  ------------------
  |  Branch (1007:33): [True: 3.69M, False: 402k]
  ------------------
 1008|   402k|                    auto sum_sizes = std::accumulate(sizes.begin(), sizes.end(), uint64_t{0});
 1009|       |                    // Sort from large to small.
 1010|   402k|                    std::ranges::sort(sizes, std::greater{});
 1011|       |                    // In the worst case, only the smallest transactions are removed.
 1012|  1.21M|                    while (sizes.size() > max_cluster_count || sum_sizes > max_cluster_size) {
  ------------------
  |  Branch (1012:28): [True: 736k, False: 473k]
  |  Branch (1012:64): [True: 71.0k, False: 402k]
  ------------------
 1013|   807k|                        sum_sizes -= sizes.back();
 1014|   807k|                        sizes.pop_back();
 1015|   807k|                        ++max_removed;
 1016|   807k|                    }
 1017|   402k|                }
 1018|       |
 1019|       |                // Invoke Trim now on the definitely-oversized txgraph.
 1020|  92.4k|                auto removed = real->Trim();
 1021|       |                // Verify that the number of removals is within range.
 1022|  92.4k|                assert(removed.size() >= 1);
  ------------------
  |  Branch (1022:17): [True: 92.4k, False: 0]
  ------------------
 1023|  92.4k|                assert(removed.size() <= max_removed);
  ------------------
  |  Branch (1023:17): [True: 92.4k, False: 0]
  ------------------
 1024|       |                // The removed set must contain all its own descendants.
 1025|  92.4k|                auto removed_set = top_sim.MakeSet(removed);
 1026|   531k|                for (auto simpos : removed_set) {
  ------------------
  |  Branch (1026:34): [True: 531k, False: 92.4k]
  ------------------
 1027|   531k|                    assert(top_sim.graph.Descendants(simpos).IsSubsetOf(removed_set));
  ------------------
  |  Branch (1027:21): [True: 531k, False: 0]
  ------------------
 1028|   531k|                }
 1029|       |                // Something from every oversized cluster should have been removed, and nothing
 1030|       |                // else.
 1031|  92.4k|                assert(top_sim.MatchesOversizedClusters(removed_set));
  ------------------
  |  Branch (1031:17): [True: 92.4k, False: 0]
  ------------------
 1032|       |
 1033|       |                // Apply all removals to the simulation, and verify the result is no longer
 1034|       |                // oversized. Don't query the real graph for oversizedness; it is compared
 1035|       |                // against the simulation anyway later.
 1036|   531k|                for (auto simpos : removed_set) {
  ------------------
  |  Branch (1036:34): [True: 531k, False: 92.4k]
  ------------------
 1037|   531k|                    top_sim.RemoveTransaction(top_sim.GetRef(simpos));
 1038|   531k|                }
 1039|  92.4k|                assert(!top_sim.IsOversized());
  ------------------
  |  Branch (1039:17): [True: 92.4k, False: 0]
  ------------------
 1040|  92.4k|                break;
 1041|   503k|            } else if (command-- == 0) {
  ------------------
  |  Branch (1041:24): [True: 22.1k, False: 481k]
  ------------------
 1042|       |                // GetMainMemoryUsage().
 1043|  22.1k|                auto usage = real->GetMainMemoryUsage();
 1044|       |                // Test stability.
 1045|  22.1k|                if (alt) {
  ------------------
  |  Branch (1045:21): [True: 11.6k, False: 10.4k]
  ------------------
 1046|  11.6k|                    auto usage2 = real->GetMainMemoryUsage();
 1047|  11.6k|                    assert(usage == usage2);
  ------------------
  |  Branch (1047:21): [True: 11.6k, False: 0]
  ------------------
 1048|  11.6k|                }
 1049|       |                // Only empty graphs have 0 memory usage.
 1050|  22.1k|                if (main_sim.GetTransactionCount() == 0) {
  ------------------
  |  Branch (1050:21): [True: 1.41k, False: 20.7k]
  ------------------
 1051|  1.41k|                    assert(usage == 0);
  ------------------
  |  Branch (1051:21): [True: 1.41k, False: 0]
  ------------------
 1052|  20.7k|                } else {
 1053|  20.7k|                    assert(usage > 0);
  ------------------
  |  Branch (1053:21): [True: 20.7k, False: 0]
  ------------------
 1054|  20.7k|                }
 1055|  22.1k|                break;
 1056|  22.1k|            }
 1057|  1.97M|        }
 1058|  1.49M|    }
 1059|       |
 1060|       |    // After running all modifications, perform an internal sanity check (before invoking
 1061|       |    // inspectors that may modify the internal state).
 1062|  18.0k|    real->SanityCheck();
 1063|       |
 1064|  18.0k|    if (!sims[0].IsOversized()) {
  ------------------
  |  Branch (1064:9): [True: 15.2k, False: 2.79k]
  ------------------
 1065|       |        // If the main graph is not oversized, verify the total ordering implied by
 1066|       |        // CompareMainOrder.
 1067|       |        // First construct two distinct randomized permutations of the positions in sims[0].
 1068|  15.2k|        std::vector<SimTxGraph::Pos> vec1;
 1069|   470k|        for (auto i : sims[0].graph.Positions()) vec1.push_back(i);
  ------------------
  |  Branch (1069:21): [True: 470k, False: 15.2k]
  ------------------
 1070|  15.2k|        std::shuffle(vec1.begin(), vec1.end(), rng);
 1071|  15.2k|        auto vec2 = vec1;
 1072|  15.2k|        std::shuffle(vec2.begin(), vec2.end(), rng);
 1073|  15.2k|        if (vec1 == vec2) std::next_permutation(vec2.begin(), vec2.end());
  ------------------
  |  Branch (1073:13): [True: 2.73k, False: 12.4k]
  ------------------
 1074|       |        // Sort both according to CompareMainOrder. By having randomized starting points, the order
 1075|       |        // of CompareMainOrder invocations is somewhat randomized as well.
 1076|  15.2k|        auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
 1077|  15.2k|            return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
 1078|  15.2k|        };
 1079|  15.2k|        std::ranges::sort(vec1, cmp);
 1080|  15.2k|        std::ranges::sort(vec2, cmp);
 1081|       |
 1082|       |        // Verify the resulting orderings are identical. This could only fail if the ordering was
 1083|       |        // not total.
 1084|  15.2k|        assert(vec1 == vec2);
  ------------------
  |  Branch (1084:9): [True: 15.2k, False: 0]
  ------------------
 1085|       |
 1086|       |        // Verify that the ordering is topological.
 1087|  15.2k|        auto todo = sims[0].graph.Positions();
 1088|   470k|        for (auto i : vec1) {
  ------------------
  |  Branch (1088:21): [True: 470k, False: 15.2k]
  ------------------
 1089|   470k|            todo.Reset(i);
 1090|   470k|            assert(!sims[0].graph.Ancestors(i).Overlaps(todo));
  ------------------
  |  Branch (1090:13): [True: 470k, False: 0]
  ------------------
 1091|   470k|        }
 1092|  15.2k|        assert(todo.None());
  ------------------
  |  Branch (1092:9): [True: 15.2k, False: 0]
  ------------------
 1093|       |
 1094|       |        // If the real graph claims to be optimal (the last DoWork() call returned true), verify
 1095|       |        // that calling Linearize on it does not improve it further.
 1096|  15.2k|        if (sims[0].real_is_optimal) {
  ------------------
  |  Branch (1096:13): [True: 2.67k, False: 12.5k]
  ------------------
 1097|  2.67k|            auto real_diagram = ChunkLinearization(sims[0].graph, vec1);
 1098|  2.67k|            auto fallback_order_sim = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
 1099|  2.67k|                auto txid_a = sims[0].GetRef(a)->m_txid;
 1100|  2.67k|                auto txid_b = sims[0].GetRef(b)->m_txid;
 1101|  2.67k|                return txid_a <=> txid_b;
 1102|  2.67k|            };
 1103|  2.67k|            auto [sim_lin, sim_optimal, _cost] = Linearize(sims[0].graph, 300000, rng.rand64(), fallback_order_sim, vec1);
 1104|  2.67k|            PostLinearize(sims[0].graph, sim_lin);
 1105|  2.67k|            auto sim_diagram = ChunkLinearization(sims[0].graph, sim_lin);
 1106|  2.67k|            auto cmp = CompareChunks(real_diagram, sim_diagram);
 1107|  2.67k|            assert(cmp == 0);
  ------------------
  |  Branch (1107:13): [True: 2.67k, False: 0]
  ------------------
 1108|       |
 1109|       |            // Verify consistency of cross-cluster chunk ordering with tie-break (equal-feerate
 1110|       |            // prefix size).
 1111|  2.67k|            auto real_chunking = ChunkLinearizationInfo(sims[0].graph, vec1);
 1112|       |            /** Map with one entry per component of the sim main graph. Key is the first Pos of the
 1113|       |             *  component. Value is the sum of all chunk sizes from that component seen
 1114|       |             *  already, at the current chunk feerate. */
 1115|  2.67k|            std::map<SimTxGraph::Pos, int32_t> comp_prefix_sizes;
 1116|       |            /** Current chunk feerate. */
 1117|  2.67k|            FeeFrac last_chunk_feerate;
 1118|       |            /** Largest seen (equal-feerate chunk prefix size, max txid).  */
 1119|  2.67k|            std::pair<int32_t, uint64_t> max_chunk_tiebreak{0, 0};
 1120|  96.7k|            for (const auto& chunk : real_chunking) {
  ------------------
  |  Branch (1120:36): [True: 96.7k, False: 2.67k]
  ------------------
 1121|       |                // If this is the first chunk with a strictly lower feerate, reset.
 1122|  96.7k|                if (ByRatio{chunk.feerate} < ByRatio{last_chunk_feerate}) {
  ------------------
  |  Branch (1122:21): [True: 17.2k, False: 79.5k]
  ------------------
 1123|  17.2k|                    comp_prefix_sizes.clear();
 1124|  17.2k|                    max_chunk_tiebreak = {0, 0};
 1125|  17.2k|                }
 1126|  96.7k|                last_chunk_feerate = chunk.feerate;
 1127|       |                // Find which sim component this chunk belongs to.
 1128|  96.7k|                auto component = sims[0].graph.GetConnectedComponent(sims[0].graph.Positions(), chunk.transactions.First());
 1129|  96.7k|                assert(chunk.transactions.IsSubsetOf(component));
  ------------------
  |  Branch (1129:17): [True: 96.7k, False: 0]
  ------------------
 1130|  96.7k|                auto comp_key = component.First();
 1131|  96.7k|                auto& comp_prefix_size = comp_prefix_sizes[comp_key];
 1132|  96.7k|                comp_prefix_size += chunk.feerate.size;
 1133|       |                // Determine the chunk's max txid.
 1134|  96.7k|                uint64_t chunk_max_txid{0};
 1135|   112k|                for (auto tx : chunk.transactions) {
  ------------------
  |  Branch (1135:30): [True: 112k, False: 96.7k]
  ------------------
 1136|   112k|                    auto txid = sims[0].GetRef(tx)->m_txid;
 1137|   112k|                    chunk_max_txid = std::max(txid, chunk_max_txid);
 1138|   112k|                }
 1139|       |                // Verify consistency: within each group of equal-feerate chunks, the
 1140|       |                // (equal-feerate chunk prefix size, max txid) must be increasing.
 1141|  96.7k|                std::pair<int32_t, uint64_t> chunk_tiebreak{comp_prefix_size, chunk_max_txid};
 1142|  96.7k|                assert(chunk_tiebreak > max_chunk_tiebreak);
  ------------------
  |  Branch (1142:17): [True: 96.7k, False: 0]
  ------------------
 1143|  96.7k|                max_chunk_tiebreak = chunk_tiebreak;
 1144|  96.7k|            }
 1145|       |
 1146|       |            // Verify that within each cluster, the internal ordering matches that of the
 1147|       |            // simulation if that is optimal too, since per-cluster optimal orderings are
 1148|       |            // deterministic. Note that both have been PostLinearize()'ed.
 1149|  2.67k|            if (sim_optimal) {
  ------------------
  |  Branch (1149:17): [True: 2.67k, False: 0]
  ------------------
 1150|  65.9k|                for (const auto& component : sims[0].GetComponents()) {
  ------------------
  |  Branch (1150:44): [True: 65.9k, False: 2.67k]
  ------------------
 1151|  65.9k|                    std::vector<DepGraphIndex> sim_chunk_lin, real_chunk_lin;
 1152|  5.43M|                    for (auto i : sim_lin) {
  ------------------
  |  Branch (1152:33): [True: 5.43M, False: 65.9k]
  ------------------
 1153|  5.43M|                        if (component[i]) sim_chunk_lin.push_back(i);
  ------------------
  |  Branch (1153:29): [True: 112k, False: 5.32M]
  ------------------
 1154|  5.43M|                    }
 1155|  5.43M|                    for (auto i : vec1) {
  ------------------
  |  Branch (1155:33): [True: 5.43M, False: 65.9k]
  ------------------
 1156|  5.43M|                        if (component[i]) real_chunk_lin.push_back(i);
  ------------------
  |  Branch (1156:29): [True: 112k, False: 5.32M]
  ------------------
 1157|  5.43M|                    }
 1158|  65.9k|                    assert(sim_chunk_lin == real_chunk_lin);
  ------------------
  |  Branch (1158:21): [True: 65.9k, False: 0]
  ------------------
 1159|  65.9k|                }
 1160|  2.67k|            }
 1161|       |
 1162|       |            // Verify that a fresh TxGraph, with the same transactions and txids, but constructed
 1163|       |            // in a different order, and with a different RNG state, recreates the exact same
 1164|       |            // ordering, showing that for optimal graphs, the full mempool ordering is
 1165|       |            // deterministic.
 1166|  2.67k|            auto real_redo = MakeTxGraph(
 1167|  2.67k|                /*max_cluster_count=*/max_cluster_count,
 1168|  2.67k|                /*max_cluster_size=*/max_cluster_size,
 1169|  2.67k|                /*acceptable_cost=*/acceptable_cost,
 1170|  2.67k|                /*fallback_order=*/fallback_order);
 1171|       |            /** Vector (indexed by SimTxGraph::Pos) of TxObjects in real_redo). */
 1172|  2.67k|            std::vector<std::optional<SimTxObject>> txobjects_redo;
 1173|  2.67k|            txobjects_redo.resize(sims[0].graph.PositionRange());
 1174|       |            // Recreate the graph's transactions with same feerate and txid.
 1175|  2.67k|            std::vector<DepGraphIndex> positions;
 1176|   112k|            for (auto i : sims[0].graph.Positions()) positions.push_back(i);
  ------------------
  |  Branch (1176:25): [True: 112k, False: 2.67k]
  ------------------
 1177|  2.67k|            std::shuffle(positions.begin(), positions.end(), rng);
 1178|   112k|            for (auto i : positions) {
  ------------------
  |  Branch (1178:25): [True: 112k, False: 2.67k]
  ------------------
 1179|   112k|                txobjects_redo[i].emplace(sims[0].GetRef(i)->m_txid);
 1180|   112k|                real_redo->AddTransaction(*txobjects_redo[i], FeePerWeight::FromFeeFrac(sims[0].graph.FeeRate(i)));
 1181|   112k|            }
 1182|       |            // Recreate the graph's dependencies.
 1183|  2.67k|            std::vector<std::pair<DepGraphIndex, DepGraphIndex>> deps;
 1184|   112k|            for (auto i : sims[0].graph.Positions()) {
  ------------------
  |  Branch (1184:25): [True: 112k, False: 2.67k]
  ------------------
 1185|   112k|                for (auto j : sims[0].graph.GetReducedParents(i)) {
  ------------------
  |  Branch (1185:29): [True: 51.7k, False: 112k]
  ------------------
 1186|  51.7k|                    deps.emplace_back(j, i);
 1187|  51.7k|                }
 1188|   112k|            }
 1189|  2.67k|            std::shuffle(deps.begin(), deps.end(), rng);
 1190|  51.7k|            for (auto [parent, child] : deps) {
  ------------------
  |  Branch (1190:39): [True: 51.7k, False: 2.67k]
  ------------------
 1191|  51.7k|                real_redo->AddDependency(*txobjects_redo[parent], *txobjects_redo[child]);
 1192|  51.7k|            }
 1193|       |            // Do work to reach optimality.
 1194|  2.67k|            if (real_redo->DoWork(300000)) {
  ------------------
  |  Branch (1194:17): [True: 2.67k, False: 0]
  ------------------
 1195|       |                // Start from a random permutation.
 1196|  2.67k|                auto vec_redo = vec1;
 1197|  2.67k|                std::shuffle(vec_redo.begin(), vec_redo.end(), rng);
 1198|  2.67k|                if (vec_redo == vec1) std::next_permutation(vec_redo.begin(), vec_redo.end());
  ------------------
  |  Branch (1198:21): [True: 239, False: 2.43k]
  ------------------
 1199|       |                // Sort it according to the main graph order in real_redo.
 1200|  2.67k|                auto cmp_redo = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
 1201|  2.67k|                    return real_redo->CompareMainOrder(*txobjects_redo[a], *txobjects_redo[b]) < 0;
 1202|  2.67k|                };
 1203|  2.67k|                std::ranges::sort(vec_redo, cmp_redo);
 1204|       |                // Compare with the ordering we got from real.
 1205|  2.67k|                assert(vec1 == vec_redo);
  ------------------
  |  Branch (1205:17): [True: 2.67k, False: 0]
  ------------------
 1206|  2.67k|            }
 1207|  2.67k|        }
 1208|       |
 1209|       |        // For every transaction in the total ordering, find a random one before it and after it,
 1210|       |        // and compare their chunk feerates, which must be consistent with the ordering.
 1211|   485k|        for (size_t pos = 0; pos < vec1.size(); ++pos) {
  ------------------
  |  Branch (1211:30): [True: 470k, False: 15.2k]
  ------------------
 1212|   470k|            auto pos_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[pos]));
 1213|   470k|            if (pos > 0) {
  ------------------
  |  Branch (1213:17): [True: 456k, False: 13.9k]
  ------------------
 1214|   456k|                size_t before = rng.randrange<size_t>(pos);
 1215|   456k|                auto before_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[before]));
 1216|   456k|                assert(ByRatio{before_feerate} >= ByRatio{pos_feerate});
  ------------------
  |  Branch (1216:17): [True: 456k, False: 0]
  ------------------
 1217|   456k|            }
 1218|   470k|            if (pos + 1 < vec1.size()) {
  ------------------
  |  Branch (1218:17): [True: 456k, False: 13.9k]
  ------------------
 1219|   456k|                size_t after = pos + 1 + rng.randrange<size_t>(vec1.size() - 1 - pos);
 1220|   456k|                auto after_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[after]));
 1221|   456k|                assert(ByRatio{after_feerate} <= ByRatio{pos_feerate});
  ------------------
  |  Branch (1221:17): [True: 456k, False: 0]
  ------------------
 1222|   456k|            }
 1223|   470k|        }
 1224|       |
 1225|       |        // The same order should be obtained through a BlockBuilder as implied by CompareMainOrder,
 1226|       |        // if nothing is skipped.
 1227|  15.2k|        auto builder = real->GetBlockBuilder();
 1228|  15.2k|        std::vector<SimTxGraph::Pos> vec_builder;
 1229|  15.2k|        std::vector<TxGraph::Ref*> last_chunk;
 1230|  15.2k|        FeePerWeight last_chunk_feerate;
 1231|   408k|        while (auto chunk = builder->GetCurrentChunk()) {
  ------------------
  |  Branch (1231:21): [True: 392k, False: 15.2k]
  ------------------
 1232|   392k|            FeePerWeight sum;
 1233|   470k|            for (TxGraph::Ref* ref : chunk->first) {
  ------------------
  |  Branch (1233:36): [True: 470k, False: 392k]
  ------------------
 1234|       |                // The reported chunk feerate must match the chunk feerate obtained by asking
 1235|       |                // it for each of the chunk's transactions individually.
 1236|   470k|                assert(real->GetMainChunkFeerate(*ref) == chunk->second);
  ------------------
  |  Branch (1236:17): [True: 470k, False: 0]
  ------------------
 1237|       |                // Verify the chunk feerate matches the sum of the reported individual feerates.
 1238|   470k|                sum += real->GetIndividualFeerate(*ref);
 1239|       |                // Chunks must contain transactions that exist in the graph.
 1240|   470k|                auto simpos = sims[0].Find(ref);
 1241|   470k|                assert(simpos != SimTxGraph::MISSING);
  ------------------
  |  Branch (1241:17): [True: 470k, False: 0]
  ------------------
 1242|   470k|                vec_builder.push_back(simpos);
 1243|   470k|            }
 1244|   392k|            assert(sum == chunk->second);
  ------------------
  |  Branch (1244:13): [True: 392k, False: 0]
  ------------------
 1245|   392k|            last_chunk = std::move(chunk->first);
 1246|   392k|            last_chunk_feerate = chunk->second;
 1247|   392k|            builder->Include();
 1248|   392k|        }
 1249|  15.2k|        assert(vec_builder == vec1);
  ------------------
  |  Branch (1249:9): [True: 15.2k, False: 0]
  ------------------
 1250|       |
 1251|       |        // The last chunk returned by the BlockBuilder must match GetWorstMainChunk, in reverse.
 1252|  15.2k|        std::reverse(last_chunk.begin(), last_chunk.end());
 1253|  15.2k|        auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk();
 1254|  15.2k|        assert(last_chunk == worst_chunk);
  ------------------
  |  Branch (1254:9): [True: 15.2k, False: 0]
  ------------------
 1255|  15.2k|        assert(last_chunk_feerate == worst_chunk_feerate);
  ------------------
  |  Branch (1255:9): [True: 15.2k, False: 0]
  ------------------
 1256|       |
 1257|       |        // Check that the implied ordering gives rise to a combined diagram that matches the
 1258|       |        // diagram constructed from the individual cluster linearization chunkings.
 1259|  15.2k|        auto main_real_diagram = get_diagram_fn(TxGraph::Level::MAIN);
 1260|  15.2k|        auto main_implied_diagram = ChunkLinearization(sims[0].graph, vec1);
 1261|  15.2k|        assert(CompareChunks(main_real_diagram, main_implied_diagram) == 0);
  ------------------
  |  Branch (1261:9): [True: 15.2k, False: 0]
  ------------------
 1262|       |
 1263|  15.2k|        if (sims.size() >= 2 && !sims[1].IsOversized()) {
  ------------------
  |  Branch (1263:13): [True: 5.76k, False: 9.45k]
  |  Branch (1263:33): [True: 5.51k, False: 252]
  ------------------
 1264|       |            // When the staging graph is not oversized as well, call GetMainStagingDiagrams, and
 1265|       |            // fully verify the result.
 1266|  5.51k|            auto [main_cmp_diagram, stage_cmp_diagram] = real->GetMainStagingDiagrams();
 1267|       |            // Check that the feerates in each diagram are monotonically decreasing.
 1268|   118k|            for (size_t i = 1; i < main_cmp_diagram.size(); ++i) {
  ------------------
  |  Branch (1268:32): [True: 112k, False: 5.51k]
  ------------------
 1269|   112k|                assert(ByRatio{main_cmp_diagram[i]} <= ByRatio{main_cmp_diagram[i - 1]});
  ------------------
  |  Branch (1269:17): [True: 112k, False: 0]
  ------------------
 1270|   112k|            }
 1271|  93.2k|            for (size_t i = 1; i < stage_cmp_diagram.size(); ++i) {
  ------------------
  |  Branch (1271:32): [True: 87.6k, False: 5.51k]
  ------------------
 1272|  87.6k|                assert(ByRatio{stage_cmp_diagram[i]} <= ByRatio{stage_cmp_diagram[i - 1]});
  ------------------
  |  Branch (1272:17): [True: 87.6k, False: 0]
  ------------------
 1273|  87.6k|            }
 1274|       |            // Treat the diagrams as sets of chunk feerates, and sort them in the same way so that
 1275|       |            // std::set_difference can be used on them below. The exact ordering does not matter
 1276|       |            // here, but it has to be consistent with the one used in main_real_diagram and
 1277|       |            // stage_real_diagram).
 1278|  5.51k|            std::ranges::sort(main_cmp_diagram, std::greater<ByRatioNegSize<FeeFrac>>{});
 1279|  5.51k|            std::ranges::sort(stage_cmp_diagram, std::greater<ByRatioNegSize<FeeFrac>>{});
 1280|       |            // Find the chunks that appear in main_diagram but are missing from main_cmp_diagram.
 1281|       |            // This is allowed, because GetMainStagingDiagrams omits clusters in main unaffected
 1282|       |            // by staging.
 1283|  5.51k|            std::vector<FeeFrac> missing_main_cmp;
 1284|  5.51k|            std::set_difference(main_real_diagram.begin(), main_real_diagram.end(),
 1285|  5.51k|                                main_cmp_diagram.begin(), main_cmp_diagram.end(),
 1286|  5.51k|                                std::inserter(missing_main_cmp, missing_main_cmp.end()),
 1287|  5.51k|                                std::greater<ByRatioNegSize<FeeFrac>>{});
 1288|  5.51k|            assert(main_cmp_diagram.size() + missing_main_cmp.size() == main_real_diagram.size());
  ------------------
  |  Branch (1288:13): [True: 5.51k, False: 0]
  ------------------
 1289|       |            // Do the same for chunks in stage_diagram missing from stage_cmp_diagram.
 1290|  5.51k|            auto stage_real_diagram = get_diagram_fn(TxGraph::Level::TOP);
 1291|  5.51k|            std::vector<FeeFrac> missing_stage_cmp;
 1292|  5.51k|            std::set_difference(stage_real_diagram.begin(), stage_real_diagram.end(),
 1293|  5.51k|                                stage_cmp_diagram.begin(), stage_cmp_diagram.end(),
 1294|  5.51k|                                std::inserter(missing_stage_cmp, missing_stage_cmp.end()),
 1295|  5.51k|                                std::greater<ByRatioNegSize<FeeFrac>>{});
 1296|  5.51k|            assert(stage_cmp_diagram.size() + missing_stage_cmp.size() == stage_real_diagram.size());
  ------------------
  |  Branch (1296:13): [True: 5.51k, False: 0]
  ------------------
 1297|       |            // The missing chunks must be equal across main & staging (otherwise they couldn't have
 1298|       |            // been omitted).
 1299|  5.51k|            assert(missing_main_cmp == missing_stage_cmp);
  ------------------
  |  Branch (1299:13): [True: 5.51k, False: 0]
  ------------------
 1300|       |
 1301|       |            // The missing part must include at least all transactions in staging which have not been
 1302|       |            // modified, or been in a cluster together with modified transactions, since they were
 1303|       |            // copied from main. Note that due to the reordering of removals w.r.t. dependency
 1304|       |            // additions, it is possible that the real implementation found more unaffected things.
 1305|  5.51k|            FeeFrac missing_real;
 1306|  59.3k|            for (const auto& feerate : missing_main_cmp) missing_real += feerate;
  ------------------
  |  Branch (1306:38): [True: 59.3k, False: 5.51k]
  ------------------
 1307|  5.51k|            FeeFrac missing_expected = sims[1].graph.FeeRate(sims[1].graph.Positions() - sims[1].modified);
 1308|       |            // Note that missing_real.fee < missing_expected.fee is possible to due the presence of
 1309|       |            // negative-fee transactions.
 1310|  5.51k|            assert(missing_real.size >= missing_expected.size);
  ------------------
  |  Branch (1310:13): [True: 5.51k, False: 0]
  ------------------
 1311|  5.51k|        }
 1312|  15.2k|    }
 1313|       |
 1314|  18.0k|    assert(real->HaveStaging() == (sims.size() > 1));
  ------------------
  |  Branch (1314:5): [True: 18.0k, False: 0]
  ------------------
 1315|       |
 1316|       |    // Try to run a full comparison, for both TxGraph::Level::MAIN and TxGraph::Level::TOP in
 1317|       |    // TxGraph inspector functions that support both.
 1318|  36.0k|    for (auto level : {TxGraph::Level::TOP, TxGraph::Level::MAIN}) {
  ------------------
  |  Branch (1318:21): [True: 36.0k, False: 18.0k]
  ------------------
 1319|  36.0k|        auto& sim = level == TxGraph::Level::TOP ? sims.back() : sims.front();
  ------------------
  |  Branch (1319:21): [True: 18.0k, False: 18.0k]
  ------------------
 1320|       |        // Compare simple properties of the graph with the simulation.
 1321|  36.0k|        assert(real->IsOversized(level) == sim.IsOversized());
  ------------------
  |  Branch (1321:9): [True: 36.0k, False: 0]
  ------------------
 1322|  36.0k|        assert(real->GetTransactionCount(level) == sim.GetTransactionCount());
  ------------------
  |  Branch (1322:9): [True: 36.0k, False: 0]
  ------------------
 1323|       |        // If the graph (and the simulation) are not oversized, perform a full comparison.
 1324|  36.0k|        if (!sim.IsOversized()) {
  ------------------
  |  Branch (1324:13): [True: 31.3k, False: 4.67k]
  ------------------
 1325|  31.3k|            auto todo = sim.graph.Positions();
 1326|       |            // Iterate over all connected components of the resulting (simulated) graph, each of which
 1327|       |            // should correspond to a cluster in the real one.
 1328|   517k|            while (todo.Any()) {
  ------------------
  |  Branch (1328:20): [True: 486k, False: 31.3k]
  ------------------
 1329|   486k|                auto component = sim.graph.FindConnectedComponent(todo);
 1330|   486k|                todo -= component;
 1331|       |                // Iterate over the transactions in that component.
 1332|   960k|                for (auto i : component) {
  ------------------
  |  Branch (1332:29): [True: 960k, False: 486k]
  ------------------
 1333|       |                    // Check its individual feerate against simulation.
 1334|   960k|                    assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
  ------------------
  |  Branch (1334:21): [True: 960k, False: 0]
  ------------------
 1335|       |                    // Check its ancestors against simulation.
 1336|   960k|                    auto expect_anc = sim.graph.Ancestors(i);
 1337|   960k|                    auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), level));
 1338|   960k|                    assert(anc.Count() <= max_cluster_count);
  ------------------
  |  Branch (1338:21): [True: 960k, False: 0]
  ------------------
 1339|   960k|                    assert(anc == expect_anc);
  ------------------
  |  Branch (1339:21): [True: 960k, False: 0]
  ------------------
 1340|       |                    // Check its descendants against simulation.
 1341|   960k|                    auto expect_desc = sim.graph.Descendants(i);
 1342|   960k|                    auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), level));
 1343|   960k|                    assert(desc.Count() <= max_cluster_count);
  ------------------
  |  Branch (1343:21): [True: 960k, False: 0]
  ------------------
 1344|   960k|                    assert(desc == expect_desc);
  ------------------
  |  Branch (1344:21): [True: 960k, False: 0]
  ------------------
 1345|       |                    // Check the cluster the transaction is part of.
 1346|   960k|                    auto cluster = real->GetCluster(*sim.GetRef(i), level);
 1347|   960k|                    assert(cluster.size() <= max_cluster_count);
  ------------------
  |  Branch (1347:21): [True: 960k, False: 0]
  ------------------
 1348|   960k|                    assert(sim.MakeSet(cluster) == component);
  ------------------
  |  Branch (1348:21): [True: 960k, False: 0]
  ------------------
 1349|       |                    // Check that the cluster is reported in a valid topological order (its
 1350|       |                    // linearization).
 1351|   960k|                    std::vector<DepGraphIndex> simlin;
 1352|   960k|                    SimTxGraph::SetType done;
 1353|   960k|                    uint64_t total_size{0};
 1354|  19.1M|                    for (TxGraph::Ref* ptr : cluster) {
  ------------------
  |  Branch (1354:44): [True: 19.1M, False: 960k]
  ------------------
 1355|  19.1M|                        auto simpos = sim.Find(ptr);
 1356|  19.1M|                        assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
  ------------------
  |  Branch (1356:25): [True: 19.1M, False: 0]
  ------------------
 1357|  19.1M|                        done.Set(simpos);
 1358|  19.1M|                        assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
  ------------------
  |  Branch (1358:25): [True: 19.1M, False: 0]
  ------------------
 1359|  19.1M|                        simlin.push_back(simpos);
 1360|  19.1M|                        total_size += sim.graph.FeeRate(simpos).size;
 1361|  19.1M|                    }
 1362|       |                    // Check cluster size.
 1363|   960k|                    assert(total_size <= max_cluster_size);
  ------------------
  |  Branch (1363:21): [True: 960k, False: 0]
  ------------------
 1364|       |                    // Construct a chunking object for the simulated graph, using the reported cluster
 1365|       |                    // linearization as ordering, and compare it against the reported chunk feerates.
 1366|   960k|                    if (sims.size() == 1 || level == TxGraph::Level::MAIN) {
  ------------------
  |  Branch (1366:25): [True: 535k, False: 424k]
  |  Branch (1366:45): [True: 202k, False: 222k]
  ------------------
 1367|   738k|                        auto simlinchunk = ChunkLinearizationInfo(sim.graph, simlin);
 1368|   738k|                        DepGraphIndex idx{0};
 1369|  7.94M|                        for (auto& chunk : simlinchunk) {
  ------------------
  |  Branch (1369:42): [True: 7.94M, False: 738k]
  ------------------
 1370|       |                            // Require that the chunks of cluster linearizations are connected (this must
 1371|       |                            // be the case as all linearizations inside are PostLinearized).
 1372|  7.94M|                            assert(sim.graph.IsConnected(chunk.transactions));
  ------------------
  |  Branch (1372:29): [True: 7.94M, False: 0]
  ------------------
 1373|       |                            // Check the chunk feerates of all transactions in the cluster.
 1374|  21.6M|                            while (chunk.transactions.Any()) {
  ------------------
  |  Branch (1374:36): [True: 13.6M, False: 7.94M]
  ------------------
 1375|  13.6M|                                assert(chunk.transactions[simlin[idx]]);
  ------------------
  |  Branch (1375:33): [True: 13.6M, False: 0]
  ------------------
 1376|  13.6M|                                chunk.transactions.Reset(simlin[idx]);
 1377|  13.6M|                                assert(chunk.feerate == real->GetMainChunkFeerate(*cluster[idx]));
  ------------------
  |  Branch (1377:33): [True: 13.6M, False: 0]
  ------------------
 1378|  13.6M|                                ++idx;
 1379|  13.6M|                            }
 1380|  7.94M|                        }
 1381|   738k|                    }
 1382|   960k|                }
 1383|   486k|            }
 1384|  31.3k|        }
 1385|  36.0k|    }
 1386|       |
 1387|       |    // Sanity check again (because invoking inspectors may modify internal unobservable state).
 1388|  18.0k|    real->SanityCheck();
 1389|       |
 1390|       |    // Kill the block builders.
 1391|  18.0k|    block_builders.clear();
 1392|       |    // Kill the TxGraph object.
 1393|  18.0k|    real.reset();
 1394|       |    // Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
 1395|       |    // can outlive the TxGraph that created them.
 1396|  18.0k|    sims.clear();
 1397|  18.0k|}
txgraph.cpp:_ZN12_GLOBAL__N_111SimTxObjectC2Ev:
   31|  18.0k|    SimTxObject() noexcept = default;
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraphC2Ejm:
   76|  18.0k|        max_cluster_count(cluster_count), max_cluster_size(cluster_size) {}
txgraph.cpp:_ZNK12_GLOBAL__N_110SimTxGraph19GetTransactionCountEv:
  121|  5.18M|    DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph14AddTransactionER7TxGraphRK10FeePerUnitI9WeightTagEm:
  151|   777k|    {
  152|   777k|        assert(graph.TxCount() < MAX_TRANSACTIONS);
  ------------------
  |  Branch (152:9): [True: 777k, False: 0]
  ------------------
  153|   777k|        auto simpos = graph.AddTransaction(feerate);
  154|   777k|        real_is_optimal = false;
  155|   777k|        MakeModified(simpos);
  156|   777k|        assert(graph.Positions()[simpos]);
  ------------------
  |  Branch (156:9): [True: 777k, False: 0]
  ------------------
  157|   777k|        simmap[simpos] = std::make_shared<SimTxObject>(txid);
  158|   777k|        txgraph.AddTransaction(*simmap[simpos], feerate);
  159|   777k|        auto ptr = simmap[simpos].get();
  160|   777k|        simrevmap[ptr] = simpos;
  161|       |        // This may invalidate our cached oversized value.
  162|   777k|        if (oversized.has_value() && !*oversized) oversized = std::nullopt;
  ------------------
  |  Branch (162:13): [True: 206k, False: 571k]
  |  Branch (162:38): [True: 114k, False: 91.3k]
  ------------------
  163|   777k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph12MakeModifiedEj:
  116|  5.24M|    {
  117|  5.24M|        modified |= graph.GetConnectedComponent(graph.Positions(), index);
  118|  5.24M|    }
txgraph.cpp:_ZN12_GLOBAL__N_111SimTxObjectC2Em:
   32|   890k|    explicit SimTxObject(uint64_t txid) noexcept : m_txid(txid) {}
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_6clEv:
  373|  1.13M|    auto pick_fn = [&]() noexcept -> SimTxObject* {
  374|  1.13M|        size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
  375|       |        /** The number of possible choices. */
  376|  1.13M|        size_t choices = tx_count[0] + sims[0].removed.size() + 1;
  377|  1.13M|        if (sims.size() == 2) {
  ------------------
  |  Branch (377:13): [True: 272k, False: 859k]
  ------------------
  378|   272k|            tx_count[1] = sims[1].GetTransactionCount();
  379|   272k|            choices += tx_count[1] + sims[1].removed.size();
  380|   272k|        }
  381|       |        /** Pick one of them. */
  382|  1.13M|        auto choice = provider.ConsumeIntegralInRange<size_t>(0, choices - 1);
  383|       |        // Consider both main and (if it exists) staging.
  384|  1.37M|        for (size_t level = 0; level < sims.size(); ++level) {
  ------------------
  |  Branch (384:32): [True: 1.24M, False: 125k]
  ------------------
  385|  1.24M|            auto& sim = sims[level];
  386|  1.24M|            if (choice < tx_count[level]) {
  ------------------
  |  Branch (386:17): [True: 963k, False: 286k]
  ------------------
  387|       |                // Return from graph.
  388|  8.82M|                for (auto i : sim.graph.Positions()) {
  ------------------
  |  Branch (388:29): [True: 8.82M, False: 0]
  ------------------
  389|  8.82M|                    if (choice == 0) return sim.GetRef(i);
  ------------------
  |  Branch (389:25): [True: 963k, False: 7.86M]
  ------------------
  390|  7.86M|                    --choice;
  391|  7.86M|                }
  392|   963k|                assert(false);
  ------------------
  |  Branch (392:17): [Folded, False: 0]
  ------------------
  393|   286k|            } else {
  394|   286k|                choice -= tx_count[level];
  395|   286k|            }
  396|   286k|            if (choice < sim.removed.size()) {
  ------------------
  |  Branch (396:17): [True: 43.0k, False: 243k]
  ------------------
  397|       |                // Return from removed.
  398|  43.0k|                return sim.removed[choice].get();
  399|   243k|            } else {
  400|   243k|                choice -= sim.removed.size();
  401|   243k|            }
  402|   286k|        }
  403|       |        // Return empty.
  404|  1.13M|        assert(choice == 0);
  ------------------
  |  Branch (404:9): [True: 125k, False: 0]
  ------------------
  405|   125k|        return &empty_ref;
  406|   125k|    };
txgraph.cpp:_ZNK12_GLOBAL__N_110SimTxGraph4FindEPKN7TxGraph3RefE:
  135|  55.5M|    {
  136|  55.5M|        auto it = simrevmap.find(ref);
  137|  55.5M|        if (it != simrevmap.end()) return it->second;
  ------------------
  |  Branch (137:13): [True: 55.2M, False: 235k]
  ------------------
  138|   235k|        return MISSING;
  139|  55.5M|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph13AddDependencyEPN7TxGraph3RefES3_:
  167|  3.68M|    {
  168|  3.68M|        auto par_pos = Find(parent);
  169|  3.68M|        if (par_pos == MISSING) return;
  ------------------
  |  Branch (169:13): [True: 3.56k, False: 3.68M]
  ------------------
  170|  3.68M|        auto chl_pos = Find(child);
  171|  3.68M|        if (chl_pos == MISSING) return;
  ------------------
  |  Branch (171:13): [True: 2.62k, False: 3.68M]
  ------------------
  172|  3.68M|        graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
  173|  3.68M|        MakeModified(par_pos);
  174|  3.68M|        real_is_optimal = false;
  175|       |        // This may invalidate our cached oversized value.
  176|  3.68M|        if (oversized.has_value() && !*oversized) oversized = std::nullopt;
  ------------------
  |  Branch (176:13): [True: 135k, False: 3.54M]
  |  Branch (176:38): [True: 111k, False: 23.5k]
  ------------------
  177|  3.68M|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph14IncludeAncDescERNSt3__16vectorIPN7TxGraph3RefENS1_9allocatorIS5_EEEEb:
  256|  75.0k|    {
  257|  75.0k|        std::vector<TxGraph::Ref*> ret;
  258|  92.6k|        for (auto ptr : arg) {
  ------------------
  |  Branch (258:23): [True: 92.6k, False: 75.0k]
  ------------------
  259|  92.6k|            auto simpos = Find(ptr);
  260|  92.6k|            if (simpos != MISSING) {
  ------------------
  |  Branch (260:17): [True: 56.1k, False: 36.5k]
  ------------------
  261|   108k|                for (auto i : desc ? graph.Descendants(simpos) : graph.Ancestors(simpos)) {
  ------------------
  |  Branch (261:29): [True: 108k, False: 56.1k]
  |  Branch (261:31): [True: 20.2k, False: 35.8k]
  ------------------
  262|   108k|                    ret.push_back(simmap[i].get());
  263|   108k|                }
  264|  56.1k|            } else {
  265|  36.5k|                ret.push_back(ptr);
  266|  36.5k|            }
  267|  92.6k|        }
  268|       |        // Construct deduplicated version in input (do not use std::sort/std::unique for
  269|       |        // deduplication as it'd rely on non-deterministic pointer comparison).
  270|  75.0k|        arg.clear();
  271|   144k|        for (auto ptr : ret) {
  ------------------
  |  Branch (271:23): [True: 144k, False: 75.0k]
  ------------------
  272|   144k|            if (std::find(arg.begin(), arg.end(), ptr) == arg.end()) {
  ------------------
  |  Branch (272:17): [True: 106k, False: 38.8k]
  ------------------
  273|   106k|                arg.push_back(ptr);
  274|   106k|            }
  275|   144k|        }
  276|  75.0k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph17RemoveTransactionEPN7TxGraph3RefE:
  191|   773k|    {
  192|   773k|        auto pos = Find(ref);
  193|   773k|        if (pos == MISSING) return;
  ------------------
  |  Branch (193:13): [True: 24.0k, False: 749k]
  ------------------
  194|   749k|        MakeModified(pos);
  195|   749k|        real_is_optimal = false;
  196|   749k|        graph.RemoveTransactions(SetType::Singleton(pos));
  197|   749k|        simrevmap.erase(simmap[pos].get());
  198|       |        // Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
  199|       |        // invoked until the simulation explicitly decided to do so.
  200|   749k|        removed.push_back(std::move(simmap[pos]));
  201|   749k|        simmap[pos].reset();
  202|       |        // This may invalidate our cached oversized value.
  203|   749k|        if (oversized.has_value() && *oversized) oversized = std::nullopt;
  ------------------
  |  Branch (203:13): [True: 124k, False: 625k]
  |  Branch (203:38): [True: 117k, False: 6.99k]
  ------------------
  204|   749k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph18DestroyTransactionEPN7TxGraph3RefEb:
  211|  43.9k|    {
  212|  43.9k|        auto pos = Find(ref);
  213|  43.9k|        if (pos == MISSING) {
  ------------------
  |  Branch (213:13): [True: 11.3k, False: 32.5k]
  ------------------
  214|       |            // Wipe the ref, if it exists, from the removed vector. Use std::partition rather
  215|       |            // than std::erase because we don't care about the order of the entries that
  216|       |            // remain.
  217|  11.3k|            auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
  218|  11.3k|            removed.erase(remove, removed.end());
  219|  32.5k|        } else {
  220|  32.5k|            MakeModified(pos);
  221|  32.5k|            graph.RemoveTransactions(SetType::Singleton(pos));
  222|  32.5k|            real_is_optimal = false;
  223|  32.5k|            simrevmap.erase(simmap[pos].get());
  224|  32.5k|            simmap[pos].reset();
  225|       |            // This may invalidate our cached oversized value.
  226|  32.5k|            if (reset_oversize && oversized.has_value() && *oversized) {
  ------------------
  |  Branch (226:17): [True: 23.0k, False: 9.56k]
  |  Branch (226:35): [True: 15.6k, False: 7.40k]
  |  Branch (226:60): [True: 5.31k, False: 10.2k]
  ------------------
  227|  5.31k|                oversized = std::nullopt;
  228|  5.31k|            }
  229|  32.5k|        }
  230|  43.9k|    }
txgraph.cpp:_ZZN12_GLOBAL__N_110SimTxGraph18DestroyTransactionEPN7TxGraph3RefEbENKUlRT_E_clINSt3__110shared_ptrINS_11SimTxObjectEEEEEDaS5_:
  217|  74.5k|            auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph17SetTransactionFeeEPN7TxGraph3RefEl:
  181|  12.4k|    {
  182|  12.4k|        auto pos = Find(ref);
  183|  12.4k|        if (pos == MISSING) return;
  ------------------
  |  Branch (183:13): [True: 2.85k, False: 9.58k]
  ------------------
  184|       |        // No need to invoke MakeModified, because this equally affects main and staging.
  185|  9.58k|        real_is_optimal = false;
  186|  9.58k|        graph.FeeRate(pos).fee = fee;
  187|  9.58k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph11IsOversizedEv:
  101|  7.61M|    {
  102|  7.61M|        if (!oversized.has_value()) {
  ------------------
  |  Branch (102:13): [True: 413k, False: 7.20M]
  ------------------
  103|       |            // Only recompute when oversized isn't already known.
  104|   413k|            oversized = false;
  105|  6.99M|            for (auto component : GetComponents()) {
  ------------------
  |  Branch (105:33): [True: 6.99M, False: 413k]
  ------------------
  106|  6.99M|                if (component.Count() > max_cluster_count) oversized = true;
  ------------------
  |  Branch (106:21): [True: 163k, False: 6.82M]
  ------------------
  107|  6.99M|                uint64_t component_size{0};
  108|  14.9M|                for (auto i : component) component_size += graph.FeeRate(i).size;
  ------------------
  |  Branch (108:29): [True: 14.9M, False: 6.99M]
  ------------------
  109|  6.99M|                if (component_size > max_cluster_size) oversized = true;
  ------------------
  |  Branch (109:21): [True: 370k, False: 6.62M]
  ------------------
  110|  6.99M|            }
  111|   413k|        }
  112|  7.61M|        return *oversized;
  113|  7.61M|    }
txgraph.cpp:_ZNK12_GLOBAL__N_110SimTxGraph6SumAllEv:
  125|  83.4k|    {
  126|  83.4k|        FeePerWeight ret;
  127|  2.77M|        for (auto i : graph.Positions()) {
  ------------------
  |  Branch (127:21): [True: 2.77M, False: 83.4k]
  ------------------
  128|  2.77M|            ret += graph.FeeRate(i);
  129|  2.77M|        }
  130|  83.4k|        return ret;
  131|  83.4k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph7MakeSetENSt3__14spanIKPN7TxGraph3RefELm18446744073709551615EEE:
  235|  3.03M|    {
  236|  3.03M|        SetType ret;
  237|  25.7M|        for (TxGraph::Ref* ptr : arg) {
  ------------------
  |  Branch (237:32): [True: 25.7M, False: 3.03M]
  ------------------
  238|  25.7M|            auto pos = Find(ptr);
  239|  25.7M|            assert(pos != Pos(-1));
  ------------------
  |  Branch (239:13): [True: 25.7M, False: 0]
  ------------------
  240|  25.7M|            ret.Set(pos);
  241|  25.7M|        }
  242|  3.03M|        return ret;
  243|  3.03M|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph10GetAncDescEPN7TxGraph3RefEb:
  247|   116k|    {
  248|   116k|        auto pos = Find(arg);
  249|   116k|        if (pos == MISSING) return {};
  ------------------
  |  Branch (249:13): [True: 27.9k, False: 88.1k]
  ------------------
  250|  88.1k|        return desc ? graph.Descendants(pos) : graph.Ancestors(pos);
  ------------------
  |  Branch (250:16): [True: 43.9k, False: 44.1k]
  ------------------
  251|   116k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraphC2ERKS0_:
   79|  58.7k|    SimTxGraph(const SimTxGraph&) noexcept = default;
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_1clIN12_GLOBAL__N_110SimTxGraphEEEDaRKT_:
  698|  4.57k|                const bool main_optimal = std::all_of(sims.cbegin(), sims.cend(), [](const auto &sim) { return sim.real_is_optimal; });
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraphaSEOS0_:
   82|  4.35k|    SimTxGraph& operator=(SimTxGraph&&) noexcept = default;
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph13GetComponentsEv:
   86|   530k|    {
   87|   530k|        auto todo = graph.Positions();
   88|   530k|        std::vector<SetType> ret;
   89|       |        // Iterate over all connected components of the graph.
   90|  10.2M|        while (todo.Any()) {
  ------------------
  |  Branch (90:16): [True: 9.70M, False: 530k]
  ------------------
   91|  9.70M|            auto component = graph.FindConnectedComponent(todo);
   92|  9.70M|            ret.push_back(component);
   93|  9.70M|            todo -= component;
   94|  9.70M|        }
   95|   530k|        return ret;
   96|   530k|    }
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEEN16BlockBuilderDataC2ENS_10unique_ptrIN7TxGraph12BlockBuilderENS_14default_deleteIS6_EEEE:
  365|  4.36k|        BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph24MatchesOversizedClustersERKN13bitset_detail14MultiIntBitSetImLj2EEE:
  282|   114k|    {
  283|   114k|        if (set.Any() && !IsOversized()) return false;
  ------------------
  |  Branch (283:13): [True: 114k, False: 0]
  |  Branch (283:26): [True: 0, False: 114k]
  ------------------
  284|       |
  285|   114k|        auto todo = graph.Positions();
  286|   114k|        if (!set.IsSubsetOf(todo)) return false;
  ------------------
  |  Branch (286:13): [True: 0, False: 114k]
  ------------------
  287|       |
  288|       |        // Walk all clusters, and make sure all of set doesn't come from non-oversized clusters
  289|   896k|        while (todo.Any()) {
  ------------------
  |  Branch (289:16): [True: 782k, False: 114k]
  ------------------
  290|   782k|            auto component = graph.FindConnectedComponent(todo);
  291|       |            // Determine whether component is oversized, due to either the size or count limit.
  292|   782k|            bool is_oversized = component.Count() > max_cluster_count;
  293|   782k|            uint64_t component_size{0};
  294|  4.50M|            for (auto i : component) component_size += graph.FeeRate(i).size;
  ------------------
  |  Branch (294:25): [True: 4.50M, False: 782k]
  ------------------
  295|   782k|            is_oversized |= component_size > max_cluster_size;
  296|       |            // Check whether overlap with set matches is_oversized.
  297|   782k|            if (is_oversized != set.Overlaps(component)) return false;
  ------------------
  |  Branch (297:17): [True: 0, False: 782k]
  ------------------
  298|   782k|            todo -= component;
  299|   782k|        }
  300|   114k|        return true;
  301|   114k|    }
txgraph.cpp:_ZN12_GLOBAL__N_110SimTxGraph6GetRefEj:
  143|   145M|    {
  144|   145M|        assert(graph.Positions()[pos]);
  ------------------
  |  Branch (144:9): [True: 145M, False: 0]
  ------------------
  145|   145M|        assert(simmap[pos]);
  ------------------
  |  Branch (145:9): [True: 145M, False: 0]
  ------------------
  146|   145M|        return simmap[pos].get();
  147|   145M|    }
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_2clIN13bitset_detail14MultiIntBitSetImLj2EEEEEDaRKT_:
  986|  56.2M|                    std::erase_if(clusters, [&](const auto& cl) noexcept { return cl == par_cluster || cl == chl_cluster; });
  ------------------
  |  Branch (986:83): [True: 1.78M, False: 54.4M]
  |  Branch (986:104): [True: 1.78M, False: 52.6M]
  ------------------
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_3clEjj:
 1076|  64.6M|        auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
 1077|  64.6M|            return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
 1078|  64.6M|        };
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_5clEjj:
 1200|  8.32M|                auto cmp_redo = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
 1201|  8.32M|                    return real_redo->CompareMainOrder(*txobjects_redo[a], *txobjects_redo[b]) < 0;
 1202|  8.32M|                };
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_7clEN7TxGraph5LevelE:
  410|  20.7k|    auto get_diagram_fn = [&](TxGraph::Level level_select) -> std::vector<FeeFrac> {
  411|  20.7k|        int level = level_select == TxGraph::Level::MAIN ? 0 : sims.size() - 1;
  ------------------
  |  Branch (411:21): [True: 15.2k, False: 5.51k]
  ------------------
  412|  20.7k|        auto& sim = sims[level];
  413|       |        // For every transaction in the graph, request its cluster, and throw them into a set.
  414|  20.7k|        std::set<std::vector<TxGraph::Ref*>> clusters;
  415|   665k|        for (auto i : sim.graph.Positions()) {
  ------------------
  |  Branch (415:21): [True: 665k, False: 20.7k]
  ------------------
  416|   665k|            auto ref = sim.GetRef(i);
  417|   665k|            clusters.insert(real->GetCluster(*ref, level_select));
  418|   665k|        }
  419|       |        // Compute the chunkings of each (deduplicated) cluster.
  420|  20.7k|        size_t num_tx{0};
  421|  20.7k|        std::vector<FeeFrac> chunk_feerates;
  422|   359k|        for (const auto& cluster : clusters) {
  ------------------
  |  Branch (422:34): [True: 359k, False: 20.7k]
  ------------------
  423|   359k|            num_tx += cluster.size();
  424|   359k|            std::vector<SimTxGraph::Pos> linearization;
  425|   359k|            linearization.reserve(cluster.size());
  426|   665k|            for (auto refptr : cluster) linearization.push_back(sim.Find(refptr));
  ------------------
  |  Branch (426:30): [True: 665k, False: 359k]
  ------------------
  427|   544k|            for (const FeeFrac& chunk_feerate : ChunkLinearization(sim.graph, linearization)) {
  ------------------
  |  Branch (427:47): [True: 544k, False: 359k]
  ------------------
  428|   544k|                chunk_feerates.push_back(chunk_feerate);
  429|   544k|            }
  430|   359k|        }
  431|       |        // Verify the number of transactions after deduplicating clusters. This implicitly verifies
  432|       |        // that GetCluster on each element of a cluster reports the cluster transactions in the same
  433|       |        // order.
  434|  20.7k|        assert(num_tx == sim.GetTransactionCount());
  ------------------
  |  Branch (434:9): [True: 20.7k, False: 0]
  ------------------
  435|       |        // Sort by feerate only, since violating topological constraints within same-feerate
  436|       |        // chunks won't affect diagram comparisons.
  437|  20.7k|        std::ranges::sort(chunk_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
  438|  20.7k|        return chunk_feerates;
  439|  20.7k|    };
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_0clERKN7TxGraph3RefES7_:
  336|  31.0M|    auto fallback_order = [&](const TxGraph::Ref& a, const TxGraph::Ref& b) noexcept {
  337|  31.0M|        uint64_t txid_a = static_cast<const SimTxObject&>(a).m_txid;
  338|  31.0M|        uint64_t txid_b = static_cast<const SimTxObject&>(b).m_txid;
  339|  31.0M|        assert(assigned_txids.contains(txid_a));
  ------------------
  |  Branch (339:9): [True: 31.0M, False: 0]
  ------------------
  340|  31.0M|        assert(assigned_txids.contains(txid_b));
  ------------------
  |  Branch (340:9): [True: 31.0M, False: 0]
  ------------------
  341|  31.0M|        return txid_a <=> txid_b;
  342|  31.0M|    };
txgraph.cpp:_ZZ19txgraph_fuzz_targetNSt3__14spanIKhLm18446744073709551615EEEENK3$_4clEjj:
 1098|   526k|            auto fallback_order_sim = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
 1099|   526k|                auto txid_a = sims[0].GetRef(a)->m_txid;
 1100|   526k|                auto txid_b = sims[0].GetRef(b)->m_txid;
 1101|   526k|                return txid_a <=> txid_b;
 1102|   526k|            };

_ZN12CheckGlobalsC2Ev:
   59|  18.0k|CheckGlobals::CheckGlobals() : m_impl(std::make_unique<CheckGlobalsImpl>()) {}
_ZN12CheckGlobalsD2Ev:
   60|  18.0k|CheckGlobals::~CheckGlobals() = default;
_ZN16CheckGlobalsImplC2Ev:
   17|  18.0k|    {
   18|  18.0k|        g_used_g_prng = false;
   19|  18.0k|        g_seeded_g_prng_zero = false;
   20|  18.0k|        g_used_system_time = false;
   21|  18.0k|        SetMockTime(0s);
   22|  18.0k|        MockableSteadyClock::ClearMockTime();
   23|  18.0k|    }
_ZN16CheckGlobalsImplD2Ev:
   25|  18.0k|    {
   26|  18.0k|        if (g_used_g_prng && !g_seeded_g_prng_zero) {
  ------------------
  |  Branch (26:13): [True: 10.5k, False: 7.46k]
  |  Branch (26:30): [True: 0, False: 10.5k]
  ------------------
   27|      0|            std::cerr << "\n\n"
   28|      0|                         "The current fuzz target used the global random state.\n\n"
   29|       |
   30|      0|                         "This is acceptable, but requires the fuzz target to call \n"
   31|      0|                         "SeedRandomStateForTest(SeedRand::ZEROS) in the first line \n"
   32|      0|                         "of the FUZZ_TARGET function.\n\n"
   33|       |
   34|      0|                         "An alternative solution would be to avoid any use of globals.\n\n"
   35|       |
   36|      0|                         "Without a solution, fuzz instability and non-determinism can lead \n"
   37|      0|                         "to non-reproducible bugs or inefficient fuzzing.\n\n"
   38|      0|                      << std::endl;
   39|      0|            std::abort(); // Abort, because AFL may try to recover from a std::exit
   40|      0|        }
   41|       |
   42|  18.0k|        if (g_used_system_time) {
  ------------------
  |  Branch (42:13): [True: 0, False: 18.0k]
  ------------------
   43|      0|            std::cerr << "\n\n"
   44|      0|                         "The current fuzz target accessed system time.\n\n"
   45|       |
   46|      0|                         "This is acceptable, but requires the fuzz target to use \n"
   47|      0|                         "a FakeNodeClock, FakeSteadyClock or call \n"
   48|      0|                         "SetMockTime() at the \n" "beginning of processing the \n"
   49|      0|                         "fuzz input.\n\n"
   50|       |
   51|      0|                         "Without setting mock time, time-dependent behavior can lead \n"
   52|      0|                         "to non-reproducible bugs or inefficient fuzzing.\n\n"
   53|      0|                      << std::endl;
   54|      0|            std::abort();
   55|      0|        }
   56|  18.0k|    }

_ZN17cluster_linearize27MaxOptimalLinearizationCostEj:
  396|   457k|{
  397|       |    // These are the largest numbers seen returned as cost by Linearize(), in a large randomized
  398|       |    // trial. There exist almost certainly far worse cases, but they are unlikely to be
  399|       |    // encountered in randomized tests. The purpose of these numbers is guaranteeing that for
  400|       |    // *some* reasonable cost bound, optimal linearizations are always found.
  401|   457k|    static constexpr uint64_t COSTS[65] = {
  402|   457k|        0,
  403|   457k|        0, 545, 928, 1633, 2647, 4065, 5598, 8258,
  404|   457k|        9505, 11471, 14137, 19553, 20460, 26191, 28397, 32599,
  405|   457k|        41631, 47419, 56329, 57767, 72196, 63652, 95366, 96537,
  406|   457k|        115653, 125407, 131734, 145090, 156349, 164665, 194224, 203953,
  407|   457k|        207710, 225878, 239971, 252284, 256534, 222142, 251332, 357098,
  408|   457k|        325788, 295867, 410053, 497483, 533892, 576572, 577845, 572400,
  409|   457k|        592536, 455082, 609249, 659130, 714091, 544507, 718788, 562378,
  410|   457k|        601926, 1025081, 732725, 708896, 738224, 900445, 1092519, 1139946
  411|   457k|    };
  412|   457k|    assert(cluster_count < std::size(COSTS));
  ------------------
  |  Branch (412:5): [True: 457k, False: 0]
  ------------------
  413|       |    // Multiply the table number by two, to account for the fact that they are not absolutes.
  414|   457k|    return COSTS[cluster_count] * 2;
  415|   457k|}

__gcov_reset:
   13|      2|extern "C" __attribute__((weak)) void __gcov_reset(void) {}

_Z22SeedRandomStateForTest8SeedRand:
   20|  18.0k|{
   21|  18.0k|    constexpr auto RANDOM_CTX_SEED{"RANDOM_CTX_SEED"};
   22|       |
   23|       |    // Do this once, on the first call, regardless of seedtype, because once
   24|       |    // MakeRandDeterministicDANGEROUS is called, the output of GetRandHash is
   25|       |    // no longer truly random. It should be enough to get the seed once for the
   26|       |    // process.
   27|  18.0k|    static const auto g_ctx_seed = []() -> std::optional<uint256> {
   28|  18.0k|        if (EnableFuzzDeterminism()) return {};
   29|       |        // If RANDOM_CTX_SEED is set, use that as seed.
   30|  18.0k|        if (const char* num{std::getenv(RANDOM_CTX_SEED)}) {
   31|  18.0k|            if (auto num_parsed{uint256::FromUserHex(num)}) {
   32|  18.0k|                return *num_parsed;
   33|  18.0k|            } else {
   34|  18.0k|                std::cerr << RANDOM_CTX_SEED << " must consist of up to " << uint256::size() * 2 << " hex digits (\"0x\" prefix allowed), it was set to: '" << num << "'.\n";
   35|  18.0k|                std::abort();
   36|  18.0k|            }
   37|  18.0k|        }
   38|       |        // Otherwise use a (truly) random value.
   39|  18.0k|        return GetRandHash();
   40|  18.0k|    }();
   41|       |
   42|  18.0k|    g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS;
   43|  18.0k|    if (EnableFuzzDeterminism()) {
  ------------------
  |  Branch (43:9): [True: 18.0k, False: 0]
  ------------------
   44|  18.0k|        Assert(g_seeded_g_prng_zero); // Only SeedRandomStateForTest(SeedRand::ZEROS) is allowed in fuzz tests
  ------------------
  |  |  116|  18.0k|#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
  ------------------
   45|  18.0k|        Assert(!g_used_g_prng);       // The global PRNG must not have been used before SeedRandomStateForTest(SeedRand::ZEROS)
  ------------------
  |  |  116|  18.0k|#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
  ------------------
   46|  18.0k|    }
   47|  18.0k|    const uint256& seed{seedtype == SeedRand::FIXED_SEED ? g_ctx_seed.value() : uint256::ZERO};
  ------------------
  |  Branch (47:25): [True: 0, False: 18.0k]
  ------------------
   48|  18.0k|    LogInfo("Setting random seed for current tests to %s=%s\n", RANDOM_CTX_SEED, seed.GetHex());
  ------------------
  |  |  125|  18.0k|#define LogInfo(...) detail_LogWithSrcLoc(BCLog::LogFlags::ALL, util::log::Level::Info, __VA_ARGS__)
  |  |  ------------------
  |  |  |  |  119|  18.0k|#define detail_LogWithSrcLoc(category, level, ...) util::log::LogPrintFormatInternal(SourceLocation{__func__}, category, level, __VA_ARGS__)
  |  |  ------------------
  ------------------
   49|  18.0k|    MakeRandDeterministicDANGEROUS(seed);
   50|  18.0k|}

_ZN10tinyformat6formatIJPKcNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEES9_NS_17FormatStringCheckIXsZT_EEEDpRKT_:
 1088|  18.0k|{
 1089|  18.0k|    std::ostringstream oss;
 1090|  18.0k|    format(oss, fmt, args...);
 1091|  18.0k|    return oss.str();
 1092|  18.0k|}
_ZN10tinyformat6formatIJPKcNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEEvRNS3_13basic_ostreamIcS6_EENS_17FormatStringCheckIXsZT_EEEDpRKT_:
 1080|  18.0k|{
 1081|  18.0k|    vformat(out, fmt, makeFormatList(args...));
 1082|  18.0k|}
_ZN10tinyformat14makeFormatListIJPKcNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEENS_6detail11FormatListNIXsZT_EEEDpRKT_:
 1044|  18.0k|{
 1045|  18.0k|    return detail::FormatListN<sizeof...(args)>(args...);
 1046|  18.0k|}
_ZN10tinyformat6detail11FormatListNILi2EEC2IJPKcNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEEEEDpRKT_:
  990|  18.0k|            : FormatList(&m_formatterStore[0], N),
  991|  18.0k|            m_formatterStore { FormatArg(args)... }
  992|  18.0k|        { static_assert(sizeof...(args) == N, "Number of args must be N"); }
_ZN10tinyformat17FormatStringCheckILj2EEcvPKcEv:
  197|  18.0k|    operator const char*() { return fmt; }
_ZN10tinyformat6detail9FormatArg10formatImplINSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEvRNS3_13basic_ostreamIcS6_EEPKcSE_iPKv:
  558|  18.0k|        {
  559|  18.0k|            formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T*>(value));
  560|  18.0k|        }
_ZN10tinyformat11formatValueINSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEEEvRNS1_13basic_ostreamIcS4_EEPKcSC_iRKT_:
  351|  18.0k|{
  352|  18.0k|#ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS
  353|       |    // Since we don't support printing of wchar_t using "%ls", make it fail at
  354|       |    // compile time in preference to printing as a void* at runtime.
  355|  18.0k|    typedef typename detail::is_wchar<T>::tinyformat_wchar_is_not_supported DummyType;
  356|  18.0k|    (void) DummyType(); // avoid unused type warning with gcc-4.8
  357|  18.0k|#endif
  358|       |    // The mess here is to support the %c and %p conversions: if these
  359|       |    // conversions are active we try to convert the type to a char or const
  360|       |    // void* respectively and format that instead of the value itself.  For the
  361|       |    // %p conversion it's important to avoid dereferencing the pointer, which
  362|       |    // could otherwise lead to a crash when printing a dangling (const char*).
  363|  18.0k|    const bool canConvertToChar = detail::is_convertible<T,char>::value;
  364|  18.0k|    const bool canConvertToVoidPtr = detail::is_convertible<T, const void*>::value;
  365|  18.0k|    if (canConvertToChar && *(fmtEnd-1) == 'c')
  ------------------
  |  Branch (365:9): [Folded, False: 18.0k]
  |  Branch (365:29): [True: 0, False: 0]
  ------------------
  366|      0|        detail::formatValueAsType<T, char>::invoke(out, value);
  367|  18.0k|    else if (canConvertToVoidPtr && *(fmtEnd-1) == 'p')
  ------------------
  |  Branch (367:14): [Folded, False: 18.0k]
  |  Branch (367:37): [True: 0, False: 0]
  ------------------
  368|      0|        detail::formatValueAsType<T, const void*>::invoke(out, value);
  369|       |#ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
  370|       |    else if (detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/;
  371|       |#endif
  372|  18.0k|    else if (ntrunc >= 0) {
  ------------------
  |  Branch (372:14): [True: 0, False: 18.0k]
  ------------------
  373|       |        // Take care not to overread C strings in truncating conversions like
  374|       |        // "%.4s" where at most 4 characters may be read.
  375|      0|        detail::formatTruncated(out, value, ntrunc);
  376|      0|    }
  377|  18.0k|    else
  378|  18.0k|        out << value;
  379|  18.0k|}
_ZN10tinyformat6detail9FormatArgC2IPKcEERKT_:
  534|  18.0k|            : m_value(static_cast<const void*>(&value)),
  535|  18.0k|            m_formatImpl(&formatImpl<T>),
  536|  18.0k|            m_toIntImpl(&toIntImpl<T>)
  537|  18.0k|        { }
_ZN10tinyformat6detail9FormatArg10formatImplIPKcEEvRNSt3__113basic_ostreamIcNS5_11char_traitsIcEEEES4_S4_iPKv:
  558|  18.0k|        {
  559|  18.0k|            formatValue(out, fmtBegin, fmtEnd, ntrunc, *static_cast<const T*>(value));
  560|  18.0k|        }
_ZN10tinyformat11formatValueIPKcEEvRNSt3__113basic_ostreamIcNS3_11char_traitsIcEEEES2_S2_iRKT_:
  351|  18.0k|{
  352|  18.0k|#ifndef TINYFORMAT_ALLOW_WCHAR_STRINGS
  353|       |    // Since we don't support printing of wchar_t using "%ls", make it fail at
  354|       |    // compile time in preference to printing as a void* at runtime.
  355|  18.0k|    typedef typename detail::is_wchar<T>::tinyformat_wchar_is_not_supported DummyType;
  356|  18.0k|    (void) DummyType(); // avoid unused type warning with gcc-4.8
  357|  18.0k|#endif
  358|       |    // The mess here is to support the %c and %p conversions: if these
  359|       |    // conversions are active we try to convert the type to a char or const
  360|       |    // void* respectively and format that instead of the value itself.  For the
  361|       |    // %p conversion it's important to avoid dereferencing the pointer, which
  362|       |    // could otherwise lead to a crash when printing a dangling (const char*).
  363|  18.0k|    const bool canConvertToChar = detail::is_convertible<T,char>::value;
  364|  18.0k|    const bool canConvertToVoidPtr = detail::is_convertible<T, const void*>::value;
  365|  18.0k|    if (canConvertToChar && *(fmtEnd-1) == 'c')
  ------------------
  |  Branch (365:9): [Folded, False: 18.0k]
  |  Branch (365:29): [True: 0, False: 0]
  ------------------
  366|      0|        detail::formatValueAsType<T, char>::invoke(out, value);
  367|  18.0k|    else if (canConvertToVoidPtr && *(fmtEnd-1) == 'p')
  ------------------
  |  Branch (367:14): [True: 18.0k, Folded]
  |  Branch (367:37): [True: 0, False: 18.0k]
  ------------------
  368|      0|        detail::formatValueAsType<T, const void*>::invoke(out, value);
  369|       |#ifdef TINYFORMAT_OLD_LIBSTDCPLUSPLUS_WORKAROUND
  370|       |    else if (detail::formatZeroIntegerWorkaround<T>::invoke(out, value)) /**/;
  371|       |#endif
  372|  18.0k|    else if (ntrunc >= 0) {
  ------------------
  |  Branch (372:14): [True: 0, False: 18.0k]
  ------------------
  373|       |        // Take care not to overread C strings in truncating conversions like
  374|       |        // "%.4s" where at most 4 characters may be read.
  375|      0|        detail::formatTruncated(out, value, ntrunc);
  376|      0|    }
  377|  18.0k|    else
  378|  18.0k|        out << value;
  379|  18.0k|}
_ZN10tinyformat17FormatStringCheckILj2EEC2EN4util21ConstevalFormatStringILj2EEE:
  196|  18.0k|    FormatStringCheck(util::ConstevalFormatString<num_params> str) : fmt{str.fmt} {}
_ZNK10tinyformat6detail9FormatArg6formatERNSt3__113basic_ostreamIcNS2_11char_traitsIcEEEEPKcS9_i:
  541|  36.0k|        {
  542|  36.0k|            TINYFORMAT_ASSERT(m_value);
  ------------------
  |  |  153|  36.0k|#   define TINYFORMAT_ASSERT(cond) assert(cond)
  ------------------
  |  Branch (542:13): [True: 36.0k, False: 0]
  ------------------
  543|  36.0k|            TINYFORMAT_ASSERT(m_formatImpl);
  ------------------
  |  |  153|  36.0k|#   define TINYFORMAT_ASSERT(cond) assert(cond)
  ------------------
  |  Branch (543:13): [True: 36.0k, False: 0]
  ------------------
  544|  36.0k|            m_formatImpl(out, fmtBegin, fmtEnd, ntrunc, m_value);
  545|  36.0k|        }
_ZN10tinyformat10FormatListC2EPNS_6detail9FormatArgEi:
  966|  18.0k|            : m_args(args), m_N(N) { }
_ZN10tinyformat6detail21parseWidthOrPrecisionERiRPKcbPKNS0_9FormatArgES1_i:
  593|  36.0k|{
  594|  36.0k|    if (*c >= '0' && *c <= '9') {
  ------------------
  |  Branch (594:9): [True: 36.0k, False: 0]
  |  Branch (594:22): [True: 0, False: 36.0k]
  ------------------
  595|      0|        n = parseIntAndAdvance(c);
  596|      0|    }
  597|  36.0k|    else if (*c == '*') {
  ------------------
  |  Branch (597:14): [True: 0, False: 36.0k]
  ------------------
  598|      0|        ++c;
  599|      0|        n = 0;
  600|      0|        if (positionalMode) {
  ------------------
  |  Branch (600:13): [True: 0, False: 0]
  ------------------
  601|      0|            int pos = parseIntAndAdvance(c) - 1;
  602|      0|            if (*c != '$')
  ------------------
  |  Branch (602:17): [True: 0, False: 0]
  ------------------
  603|      0|                TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  604|      0|            if (pos >= 0 && pos < numArgs)
  ------------------
  |  Branch (604:17): [True: 0, False: 0]
  |  Branch (604:29): [True: 0, False: 0]
  ------------------
  605|      0|                n = args[pos].toInt();
  606|      0|            else
  607|      0|                TINYFORMAT_ERROR("tinyformat: Positional argument out of range");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  608|      0|            ++c;
  609|      0|        }
  610|      0|        else {
  611|      0|            if (argIndex < numArgs)
  ------------------
  |  Branch (611:17): [True: 0, False: 0]
  ------------------
  612|      0|                n = args[argIndex++].toInt();
  613|      0|            else
  614|      0|                TINYFORMAT_ERROR("tinyformat: Not enough arguments to read variable width or precision");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  615|      0|        }
  616|      0|    }
  617|  36.0k|    else {
  618|  36.0k|        return false;
  619|  36.0k|    }
  620|      0|    return true;
  621|  36.0k|}
_ZN10tinyformat6detail24printFormatStringLiteralERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEEPKc:
  629|  54.0k|{
  630|  54.0k|    const char* c = fmt;
  631|   828k|    for (;; ++c) {
  632|   828k|        if (*c == '\0') {
  ------------------
  |  Branch (632:13): [True: 18.0k, False: 810k]
  ------------------
  633|  18.0k|            out.write(fmt, c - fmt);
  634|  18.0k|            return c;
  635|  18.0k|        }
  636|   810k|        else if (*c == '%') {
  ------------------
  |  Branch (636:18): [True: 36.0k, False: 774k]
  ------------------
  637|  36.0k|            out.write(fmt, c - fmt);
  638|  36.0k|            if (*(c+1) != '%')
  ------------------
  |  Branch (638:17): [True: 36.0k, False: 0]
  ------------------
  639|  36.0k|                return c;
  640|       |            // for "%%", tack trailing % onto next literal section.
  641|      0|            fmt = ++c;
  642|      0|        }
  643|   828k|    }
  644|  54.0k|}
_ZN10tinyformat6detail21streamStateFromFormatERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEERbS7_RiPKcPKNS0_9FormatArgES8_i:
  685|  36.0k|{
  686|  36.0k|    TINYFORMAT_ASSERT(*fmtStart == '%');
  ------------------
  |  |  153|  36.0k|#   define TINYFORMAT_ASSERT(cond) assert(cond)
  ------------------
  |  Branch (686:5): [True: 36.0k, False: 0]
  ------------------
  687|       |    // Reset stream state to defaults.
  688|  36.0k|    out.width(0);
  689|  36.0k|    out.precision(6);
  690|  36.0k|    out.fill(' ');
  691|       |    // Reset most flags; ignore irrelevant unitbuf & skipws.
  692|  36.0k|    out.unsetf(std::ios::adjustfield | std::ios::basefield |
  693|  36.0k|               std::ios::floatfield | std::ios::showbase | std::ios::boolalpha |
  694|  36.0k|               std::ios::showpoint | std::ios::showpos | std::ios::uppercase);
  695|  36.0k|    bool precisionSet = false;
  696|  36.0k|    bool widthSet = false;
  697|  36.0k|    int widthExtra = 0;
  698|  36.0k|    const char* c = fmtStart + 1;
  699|       |
  700|       |    // 1) Parse an argument index (if followed by '$') or a width possibly
  701|       |    // preceded with '0' flag.
  702|  36.0k|    if (*c >= '0' && *c <= '9') {
  ------------------
  |  Branch (702:9): [True: 36.0k, False: 0]
  |  Branch (702:22): [True: 0, False: 36.0k]
  ------------------
  703|      0|        const char tmpc = *c;
  704|      0|        int value = parseIntAndAdvance(c);
  705|      0|        if (*c == '$') {
  ------------------
  |  Branch (705:13): [True: 0, False: 0]
  ------------------
  706|       |            // value is an argument index
  707|      0|            if (value > 0 && value <= numArgs)
  ------------------
  |  Branch (707:17): [True: 0, False: 0]
  |  Branch (707:30): [True: 0, False: 0]
  ------------------
  708|      0|                argIndex = value - 1;
  709|      0|            else
  710|      0|                TINYFORMAT_ERROR("tinyformat: Positional argument out of range");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  711|      0|            ++c;
  712|      0|            positionalMode = true;
  713|      0|        }
  714|      0|        else if (positionalMode) {
  ------------------
  |  Branch (714:18): [True: 0, False: 0]
  ------------------
  715|      0|            TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  716|      0|        }
  717|      0|        else {
  718|      0|            if (tmpc == '0') {
  ------------------
  |  Branch (718:17): [True: 0, False: 0]
  ------------------
  719|       |                // Use internal padding so that numeric values are
  720|       |                // formatted correctly, eg -00010 rather than 000-10
  721|      0|                out.fill('0');
  722|      0|                out.setf(std::ios::internal, std::ios::adjustfield);
  723|      0|            }
  724|      0|            if (value != 0) {
  ------------------
  |  Branch (724:17): [True: 0, False: 0]
  ------------------
  725|       |                // Nonzero value means that we parsed width.
  726|      0|                widthSet = true;
  727|      0|                out.width(value);
  728|      0|            }
  729|      0|        }
  730|      0|    }
  731|  36.0k|    else if (positionalMode) {
  ------------------
  |  Branch (731:14): [True: 0, False: 36.0k]
  ------------------
  732|      0|        TINYFORMAT_ERROR("tinyformat: Non-positional argument used after a positional one");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  733|      0|    }
  734|       |    // 2) Parse flags and width if we did not do it in previous step.
  735|  36.0k|    if (!widthSet) {
  ------------------
  |  Branch (735:9): [True: 36.0k, False: 0]
  ------------------
  736|       |        // Parse flags
  737|  36.0k|        for (;; ++c) {
  738|  36.0k|            switch (*c) {
  739|      0|                case '#':
  ------------------
  |  Branch (739:17): [True: 0, False: 36.0k]
  ------------------
  740|      0|                    out.setf(std::ios::showpoint | std::ios::showbase);
  741|      0|                    continue;
  742|      0|                case '0':
  ------------------
  |  Branch (742:17): [True: 0, False: 36.0k]
  ------------------
  743|       |                    // overridden by left alignment ('-' flag)
  744|      0|                    if (!(out.flags() & std::ios::left)) {
  ------------------
  |  Branch (744:25): [True: 0, False: 0]
  ------------------
  745|       |                        // Use internal padding so that numeric values are
  746|       |                        // formatted correctly, eg -00010 rather than 000-10
  747|      0|                        out.fill('0');
  748|      0|                        out.setf(std::ios::internal, std::ios::adjustfield);
  749|      0|                    }
  750|      0|                    continue;
  751|      0|                case '-':
  ------------------
  |  Branch (751:17): [True: 0, False: 36.0k]
  ------------------
  752|      0|                    out.fill(' ');
  753|      0|                    out.setf(std::ios::left, std::ios::adjustfield);
  754|      0|                    continue;
  755|      0|                case ' ':
  ------------------
  |  Branch (755:17): [True: 0, False: 36.0k]
  ------------------
  756|       |                    // overridden by show positive sign, '+' flag.
  757|      0|                    if (!(out.flags() & std::ios::showpos))
  ------------------
  |  Branch (757:25): [True: 0, False: 0]
  ------------------
  758|      0|                        spacePadPositive = true;
  759|      0|                    continue;
  760|      0|                case '+':
  ------------------
  |  Branch (760:17): [True: 0, False: 36.0k]
  ------------------
  761|      0|                    out.setf(std::ios::showpos);
  762|      0|                    spacePadPositive = false;
  763|      0|                    widthExtra = 1;
  764|      0|                    continue;
  765|  36.0k|                default:
  ------------------
  |  Branch (765:17): [True: 36.0k, False: 0]
  ------------------
  766|  36.0k|                    break;
  767|  36.0k|            }
  768|  36.0k|            break;
  769|  36.0k|        }
  770|       |        // Parse width
  771|  36.0k|        int width = 0;
  772|  36.0k|        widthSet = parseWidthOrPrecision(width, c, positionalMode,
  773|  36.0k|                                         args, argIndex, numArgs);
  774|  36.0k|        if (widthSet) {
  ------------------
  |  Branch (774:13): [True: 0, False: 36.0k]
  ------------------
  775|      0|            if (width < 0) {
  ------------------
  |  Branch (775:17): [True: 0, False: 0]
  ------------------
  776|       |                // negative widths correspond to '-' flag set
  777|      0|                out.fill(' ');
  778|      0|                out.setf(std::ios::left, std::ios::adjustfield);
  779|      0|                width = -width;
  780|      0|            }
  781|      0|            out.width(width);
  782|      0|        }
  783|  36.0k|    }
  784|       |    // 3) Parse precision
  785|  36.0k|    if (*c == '.') {
  ------------------
  |  Branch (785:9): [True: 0, False: 36.0k]
  ------------------
  786|      0|        ++c;
  787|      0|        int precision = 0;
  788|      0|        parseWidthOrPrecision(precision, c, positionalMode,
  789|      0|                              args, argIndex, numArgs);
  790|       |        // Presence of `.` indicates precision set, unless the inferred value
  791|       |        // was negative in which case the default is used.
  792|      0|        precisionSet = precision >= 0;
  793|      0|        if (precisionSet)
  ------------------
  |  Branch (793:13): [True: 0, False: 0]
  ------------------
  794|      0|            out.precision(precision);
  795|      0|    }
  796|       |    // 4) Ignore any C99 length modifier
  797|  36.0k|    while (*c == 'l' || *c == 'h' || *c == 'L' ||
  ------------------
  |  Branch (797:12): [True: 0, False: 36.0k]
  |  Branch (797:25): [True: 0, False: 36.0k]
  |  Branch (797:38): [True: 0, False: 36.0k]
  ------------------
  798|  36.0k|           *c == 'j' || *c == 'z' || *c == 't') {
  ------------------
  |  Branch (798:12): [True: 0, False: 36.0k]
  |  Branch (798:25): [True: 0, False: 36.0k]
  |  Branch (798:38): [True: 0, False: 36.0k]
  ------------------
  799|      0|        ++c;
  800|      0|    }
  801|       |    // 5) We're up to the conversion specifier character.
  802|       |    // Set stream flags based on conversion specifier (thanks to the
  803|       |    // boost::format class for forging the way here).
  804|  36.0k|    bool intConversion = false;
  805|  36.0k|    switch (*c) {
  806|      0|        case 'u': case 'd': case 'i':
  ------------------
  |  Branch (806:9): [True: 0, False: 36.0k]
  |  Branch (806:19): [True: 0, False: 36.0k]
  |  Branch (806:29): [True: 0, False: 36.0k]
  ------------------
  807|      0|            out.setf(std::ios::dec, std::ios::basefield);
  808|      0|            intConversion = true;
  809|      0|            break;
  810|      0|        case 'o':
  ------------------
  |  Branch (810:9): [True: 0, False: 36.0k]
  ------------------
  811|      0|            out.setf(std::ios::oct, std::ios::basefield);
  812|      0|            intConversion = true;
  813|      0|            break;
  814|      0|        case 'X':
  ------------------
  |  Branch (814:9): [True: 0, False: 36.0k]
  ------------------
  815|      0|            out.setf(std::ios::uppercase);
  816|      0|            [[fallthrough]];
  817|      0|        case 'x': case 'p':
  ------------------
  |  Branch (817:9): [True: 0, False: 36.0k]
  |  Branch (817:19): [True: 0, False: 36.0k]
  ------------------
  818|      0|            out.setf(std::ios::hex, std::ios::basefield);
  819|      0|            intConversion = true;
  820|      0|            break;
  821|      0|        case 'E':
  ------------------
  |  Branch (821:9): [True: 0, False: 36.0k]
  ------------------
  822|      0|            out.setf(std::ios::uppercase);
  823|      0|            [[fallthrough]];
  824|      0|        case 'e':
  ------------------
  |  Branch (824:9): [True: 0, False: 36.0k]
  ------------------
  825|      0|            out.setf(std::ios::scientific, std::ios::floatfield);
  826|      0|            out.setf(std::ios::dec, std::ios::basefield);
  827|      0|            break;
  828|      0|        case 'F':
  ------------------
  |  Branch (828:9): [True: 0, False: 36.0k]
  ------------------
  829|      0|            out.setf(std::ios::uppercase);
  830|      0|            [[fallthrough]];
  831|      0|        case 'f':
  ------------------
  |  Branch (831:9): [True: 0, False: 36.0k]
  ------------------
  832|      0|            out.setf(std::ios::fixed, std::ios::floatfield);
  833|      0|            break;
  834|      0|        case 'A':
  ------------------
  |  Branch (834:9): [True: 0, False: 36.0k]
  ------------------
  835|      0|            out.setf(std::ios::uppercase);
  836|      0|            [[fallthrough]];
  837|      0|        case 'a':
  ------------------
  |  Branch (837:9): [True: 0, False: 36.0k]
  ------------------
  838|       |#           ifdef _MSC_VER
  839|       |            // Workaround https://developercommunity.visualstudio.com/content/problem/520472/hexfloat-stream-output-does-not-ignore-precision-a.html
  840|       |            // by always setting maximum precision on MSVC to avoid precision
  841|       |            // loss for doubles.
  842|       |            out.precision(13);
  843|       |#           endif
  844|      0|            out.setf(std::ios::fixed | std::ios::scientific, std::ios::floatfield);
  845|      0|            break;
  846|      0|        case 'G':
  ------------------
  |  Branch (846:9): [True: 0, False: 36.0k]
  ------------------
  847|      0|            out.setf(std::ios::uppercase);
  848|      0|            [[fallthrough]];
  849|      0|        case 'g':
  ------------------
  |  Branch (849:9): [True: 0, False: 36.0k]
  ------------------
  850|      0|            out.setf(std::ios::dec, std::ios::basefield);
  851|       |            // As in boost::format, let stream decide float format.
  852|      0|            out.flags(out.flags() & ~std::ios::floatfield);
  853|      0|            break;
  854|      0|        case 'c':
  ------------------
  |  Branch (854:9): [True: 0, False: 36.0k]
  ------------------
  855|       |            // Handled as special case inside formatValue()
  856|      0|            break;
  857|  36.0k|        case 's':
  ------------------
  |  Branch (857:9): [True: 36.0k, False: 0]
  ------------------
  858|  36.0k|            if (precisionSet)
  ------------------
  |  Branch (858:17): [True: 0, False: 36.0k]
  ------------------
  859|      0|                ntrunc = static_cast<int>(out.precision());
  860|       |            // Make %s print Booleans as "true" and "false"
  861|  36.0k|            out.setf(std::ios::boolalpha);
  862|  36.0k|            break;
  863|      0|        case 'n':
  ------------------
  |  Branch (863:9): [True: 0, False: 36.0k]
  ------------------
  864|       |            // Not supported - will cause problems!
  865|      0|            TINYFORMAT_ERROR("tinyformat: %n conversion spec not supported");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  866|      0|            break;
  867|      0|        case '\0':
  ------------------
  |  Branch (867:9): [True: 0, False: 36.0k]
  ------------------
  868|      0|            TINYFORMAT_ERROR("tinyformat: Conversion spec incorrectly "
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  869|      0|                             "terminated by end of string");
  870|      0|            return c;
  871|      0|        default:
  ------------------
  |  Branch (871:9): [True: 0, False: 36.0k]
  ------------------
  872|      0|            break;
  873|  36.0k|    }
  874|  36.0k|    if (intConversion && precisionSet && !widthSet) {
  ------------------
  |  Branch (874:9): [True: 0, False: 36.0k]
  |  Branch (874:26): [True: 0, False: 0]
  |  Branch (874:42): [True: 0, False: 0]
  ------------------
  875|       |        // "precision" for integers gives the minimum number of digits (to be
  876|       |        // padded with zeros on the left).  This isn't really supported by the
  877|       |        // iostreams, but we can approximately simulate it with the width if
  878|       |        // the width isn't otherwise used.
  879|      0|        out.width(out.precision() + widthExtra);
  880|      0|        out.setf(std::ios::internal, std::ios::adjustfield);
  881|      0|        out.fill('0');
  882|      0|    }
  883|  36.0k|    return c+1;
  884|  36.0k|}
_ZN10tinyformat6detail10formatImplERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEEPKcPKNS0_9FormatArgEi:
  891|  18.0k|{
  892|       |    // Saved stream state
  893|  18.0k|    std::streamsize origWidth = out.width();
  894|  18.0k|    std::streamsize origPrecision = out.precision();
  895|  18.0k|    std::ios::fmtflags origFlags = out.flags();
  896|  18.0k|    char origFill = out.fill();
  897|       |
  898|       |    // "Positional mode" means all format specs should be of the form "%n$..."
  899|       |    // with `n` an integer. We detect this in `streamStateFromFormat`.
  900|  18.0k|    bool positionalMode = false;
  901|  18.0k|    int argIndex = 0;
  902|  54.0k|    while (true) {
  ------------------
  |  Branch (902:12): [True: 54.0k, Folded]
  ------------------
  903|  54.0k|        fmt = printFormatStringLiteral(out, fmt);
  904|  54.0k|        if (*fmt == '\0') {
  ------------------
  |  Branch (904:13): [True: 18.0k, False: 36.0k]
  ------------------
  905|  18.0k|            if (!positionalMode && argIndex < numArgs) {
  ------------------
  |  Branch (905:17): [True: 18.0k, False: 0]
  |  Branch (905:36): [True: 0, False: 18.0k]
  ------------------
  906|      0|                TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  907|      0|            }
  908|  18.0k|            break;
  909|  18.0k|        }
  910|  36.0k|        bool spacePadPositive = false;
  911|  36.0k|        int ntrunc = -1;
  912|  36.0k|        const char* fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt,
  913|  36.0k|                                                   args, argIndex, numArgs);
  914|       |        // NB: argIndex may be incremented by reading variable width/precision
  915|       |        // in `streamStateFromFormat`, so do the bounds check here.
  916|  36.0k|        if (argIndex >= numArgs) {
  ------------------
  |  Branch (916:13): [True: 0, False: 36.0k]
  ------------------
  917|      0|            TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string");
  ------------------
  |  |  135|      0|#define TINYFORMAT_ERROR(reasonString) throw tinyformat::format_error(reasonString)
  ------------------
  918|      0|            return;
  919|      0|        }
  920|  36.0k|        const FormatArg& arg = args[argIndex];
  921|       |        // Format the arg into the stream.
  922|  36.0k|        if (!spacePadPositive) {
  ------------------
  |  Branch (922:13): [True: 36.0k, False: 0]
  ------------------
  923|  36.0k|            arg.format(out, fmt, fmtEnd, ntrunc);
  924|  36.0k|        }
  925|      0|        else {
  926|       |            // The following is a special case with no direct correspondence
  927|       |            // between stream formatting and the printf() behaviour.  Simulate
  928|       |            // it crudely by formatting into a temporary string stream and
  929|       |            // munging the resulting string.
  930|      0|            std::ostringstream tmpStream;
  931|      0|            tmpStream.copyfmt(out);
  932|      0|            tmpStream.setf(std::ios::showpos);
  933|      0|            arg.format(tmpStream, fmt, fmtEnd, ntrunc);
  934|      0|            std::string result = tmpStream.str(); // allocates... yuck.
  935|      0|            for (size_t i = 0, iend = result.size(); i < iend; ++i) {
  ------------------
  |  Branch (935:54): [True: 0, False: 0]
  ------------------
  936|      0|                if (result[i] == '+')
  ------------------
  |  Branch (936:21): [True: 0, False: 0]
  ------------------
  937|      0|                    result[i] = ' ';
  938|      0|            }
  939|      0|            out << result;
  940|      0|        }
  941|  36.0k|        if (!positionalMode)
  ------------------
  |  Branch (941:13): [True: 36.0k, False: 0]
  ------------------
  942|  36.0k|            ++argIndex;
  943|  36.0k|        fmt = fmtEnd;
  944|  36.0k|    }
  945|       |
  946|       |    // Restore stream state
  947|  18.0k|    out.width(origWidth);
  948|  18.0k|    out.precision(origPrecision);
  949|  18.0k|    out.flags(origFlags);
  950|  18.0k|    out.fill(origFill);
  951|  18.0k|}
_ZN10tinyformat7vformatERNSt3__113basic_ostreamIcNS0_11char_traitsIcEEEEPKcRKNS_10FormatListE:
 1070|  18.0k|{
 1071|  18.0k|    detail::formatImpl(out, fmt, list.m_args, list.m_N);
 1072|  18.0k|}
_ZN10tinyformat6detail9FormatArgC2INSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEERKT_:
  534|  18.0k|            : m_value(static_cast<const void*>(&value)),
  535|  18.0k|            m_formatImpl(&formatImpl<T>),
  536|  18.0k|            m_toIntImpl(&toIntImpl<T>)
  537|  18.0k|        { }

_ZN7TxGraph3RefD2Ev:
 3566|   908k|{
 3567|   908k|    if (m_graph) {
  ------------------
  |  Branch (3567:9): [True: 187k, False: 721k]
  ------------------
 3568|       |        // Inform the TxGraph about the Ref being destroyed.
 3569|   187k|        m_graph->UnlinkRef(m_index);
 3570|   187k|        m_graph = nullptr;
 3571|   187k|    }
 3572|   908k|}
_Z11MakeTxGraphjmmRKNSt3__18functionIFNS_15strong_orderingERKN7TxGraph3RefES5_EEE:
 3588|  20.6k|{
 3589|  20.6k|    return std::make_unique<TxGraphImpl>(
 3590|  20.6k|        /*max_cluster_count=*/max_cluster_count,
 3591|  20.6k|        /*max_cluster_size=*/max_cluster_size,
 3592|  20.6k|        /*acceptable_cost=*/acceptable_cost,
 3593|  20.6k|        /*fallback_order=*/fallback_order);
 3594|  20.6k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImplC2EjmmRKNSt3__18functionIFNS1_15strong_orderingERKN7TxGraph3RefES7_EEE:
  641|  20.6k|        m_max_cluster_count(max_cluster_count),
  642|  20.6k|        m_max_cluster_size(max_cluster_size),
  643|  20.6k|        m_acceptable_cost(acceptable_cost),
  644|  20.6k|        m_fallback_order(fallback_order),
  645|  20.6k|        m_main_chunkindex(ChunkOrder(this))
  646|  20.6k|    {
  647|  20.6k|        Assume(max_cluster_count >= 1);
  ------------------
  |  |  128|  20.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  648|  20.6k|        Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
  ------------------
  |  |  128|  20.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  649|  20.6k|    }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl10ClusterSetC2Ev:
  458|  79.3k|        ClusterSet() noexcept = default;
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl10ChunkOrderC2EPKS0_:
  531|  20.6k|        explicit ChunkOrder(const TxGraphImpl* graph) : m_graph(graph) {}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImplD2Ev:
 1602|  20.6k|{
 1603|       |    // If Refs outlive the TxGraphImpl they refer to, unlink them, so that their destructor does not
 1604|       |    // try to reach into a non-existing TxGraphImpl anymore.
 1605|   827k|    for (auto& entry : m_entries) {
  ------------------
  |  Branch (1605:22): [True: 827k, False: 20.6k]
  ------------------
 1606|   827k|        if (entry.m_ref != nullptr) {
  ------------------
  |  Branch (1606:13): [True: 703k, False: 124k]
  ------------------
 1607|   703k|            GetRefGraph(*entry.m_ref) = nullptr;
 1608|   703k|        }
 1609|   827k|    }
 1610|  20.6k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl14AddTransactionERN7TxGraph3RefERK10FeePerUnitI9WeightTagE:
 2231|   890k|{
 2232|   890k|    Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
  ------------------
  |  |  128|   901k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 879k, False: 10.9k]
  |  |  |  Branch (128:51): [True: 10.9k, False: 0]
  |  |  ------------------
  ------------------
 2233|   890k|    Assume(feerate.size > 0);
  ------------------
  |  |  128|   890k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2234|   890k|    Assume(GetRefGraph(arg) == nullptr);
  ------------------
  |  |  128|   890k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2235|       |    // Construct a new Entry, and link it with the Ref.
 2236|   890k|    auto idx = m_entries.size();
 2237|   890k|    m_entries.emplace_back();
 2238|   890k|    auto& entry = m_entries.back();
 2239|   890k|    entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
 2240|   890k|    entry.m_ref = &arg;
 2241|   890k|    GetRefGraph(arg) = this;
 2242|   890k|    GetRefIndex(arg) = idx;
 2243|       |    // Construct a new singleton Cluster (which is necessarily optimally linearized).
 2244|   890k|    bool oversized = uint64_t(feerate.size) > m_max_cluster_size;
 2245|   890k|    auto cluster = CreateEmptyCluster(1);
 2246|   890k|    cluster->AppendTransaction(idx, feerate);
 2247|   890k|    auto cluster_ptr = cluster.get();
 2248|   890k|    int level = GetTopLevel();
 2249|   890k|    auto& clusterset = GetClusterSet(level);
 2250|   890k|    InsertCluster(level, std::move(cluster), oversized ? QualityLevel::OVERSIZED_SINGLETON : QualityLevel::OPTIMAL);
  ------------------
  |  Branch (2250:46): [True: 88.6k, False: 802k]
  ------------------
 2251|   890k|    cluster_ptr->Updated(*this, /*level=*/level, /*rename=*/false);
 2252|   890k|    clusterset.m_cluster_usage += cluster_ptr->TotalMemoryUsage();
 2253|   890k|    ++clusterset.m_txcount;
 2254|       |    // Deal with individually oversized transactions.
 2255|   890k|    if (oversized) {
  ------------------
  |  Branch (2255:9): [True: 88.6k, False: 802k]
  ------------------
 2256|  88.6k|        ++clusterset.m_txcount_oversized;
 2257|  88.6k|        clusterset.m_oversized = true;
 2258|  88.6k|        clusterset.m_group_data = std::nullopt;
 2259|  88.6k|    }
 2260|   890k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl11GetTopLevelEv:
  679|  79.8M|    int GetTopLevel() const noexcept { return m_staging_clusterset.has_value(); }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl18CreateEmptyClusterEj:
  708|  1.12M|    {
  709|  1.12M|        if (tx_count >= SingletonClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= SingletonClusterImpl::MAX_TX_COUNT) {
  ------------------
  |  Branch (709:13): [True: 1.12M, False: 0]
  |  Branch (709:72): [True: 927k, False: 197k]
  ------------------
  710|   927k|            return CreateEmptySingletonCluster();
  711|   927k|        }
  712|   197k|        if (tx_count >= GenericClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= GenericClusterImpl::MAX_TX_COUNT) {
  ------------------
  |  Branch (712:13): [True: 197k, False: 0]
  |  Branch (712:70): [True: 197k, False: 0]
  ------------------
  713|   197k|            return CreateEmptyGenericCluster();
  714|   197k|        }
  715|   197k|        assert(false);
  ------------------
  |  Branch (715:9): [Folded, False: 0]
  ------------------
  716|      0|        return {};
  717|      0|    }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl27CreateEmptySingletonClusterEv:
  702|  2.40M|    {
  703|  2.40M|        return std::make_unique<SingletonClusterImpl>(m_next_sequence_counter++);
  704|  2.40M|    }
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImplC2Em:
  331|  2.40M|    explicit SingletonClusterImpl(uint64_t sequence) noexcept : Cluster(sequence) {}
txgraph.cpp:_ZN12_GLOBAL__N_17ClusterC2Em:
  119|  2.62M|    explicit Cluster(uint64_t sequence) noexcept : m_sequence(sequence) {}
txgraph.cpp:_ZN12_GLOBAL__N_17ClusterD2Ev:
  123|  2.62M|    virtual ~Cluster() = default;
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl21GetMinIntendedTxCountEv:
  334|   769k|    constexpr DepGraphIndex GetMinIntendedTxCount() const noexcept final { return MIN_INTENDED_TX_COUNT; }
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl13GetMaxTxCountEv:
  335|   944k|    constexpr DepGraphIndex GetMaxTxCount() const noexcept final { return MAX_TX_COUNT; }
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl16TotalMemoryUsageEv:
  928|  5.15M|{
  929|  5.15M|    return // Memory usage of the allocated SingletonClusterImpl itself.
  930|  5.15M|           memusage::MallocUsage(sizeof(SingletonClusterImpl)) +
  931|       |           // Memory usage of the ClusterSet::m_clusters entry.
  932|  5.15M|           sizeof(std::unique_ptr<Cluster>);
  933|  5.15M|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl21GetDepGraphIndexRangeEv:
  337|  1.51M|    DepGraphIndex GetDepGraphIndexRange() const noexcept final { return GetTxCount(); }
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl10GetTxCountEv:
  336|  34.9M|    LinearizationIndex GetTxCount() const noexcept final { return m_graph_index != NO_GRAPH_INDEX; }
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl14GetTotalTxSizeEv:
  338|  4.61M|    uint64_t GetTotalTxSize() const noexcept final { return GetTxCount() ? m_feerate.size : 0; }
  ------------------
  |  Branch (338:61): [True: 4.59M, False: 14.3k]
  ------------------
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl15GetClusterEntryEj:
  339|  1.25M|    GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { Assume(index == 0); Assume(GetTxCount()); return m_graph_index; }
  ------------------
  |  |  128|  1.25M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
                  GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { Assume(index == 0); Assume(GetTxCount()); return m_graph_index; }
  ------------------
  |  |  128|  1.25M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl17AppendTransactionEj10FeePerUnitI9WeightTagE:
  954|   927k|{
  955|   927k|    Assume(!GetTxCount());
  ------------------
  |  |  128|   927k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  956|   927k|    m_graph_index = graph_idx;
  957|   927k|    m_feerate = feerate;
  958|   927k|    return 0;
  959|   927k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl15AddDependenciesEN13bitset_detail9IntBitSetImEEj:
  967|  37.1k|{
  968|       |    // Singletons cannot have any dependencies.
  969|  37.1k|    Assume(child == 0);
  ------------------
  |  |  128|  37.1k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  970|  37.1k|    Assume(parents == SetType{} || parents == SetType::Fill(0));
  ------------------
  |  |  128|  37.1k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 37.1k, False: 0]
  |  |  |  Branch (128:51): [True: 0, False: 0]
  |  |  ------------------
  ------------------
  971|  37.1k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl19ExtractTransactionsERKNSt3__18functionIFvjj10FeePerUnitI9WeightTagEEEERKNS2_IFvjjN13bitset_detail9IntBitSetImEEEEE:
  988|  1.51M|{
  989|  1.51M|    if (GetTxCount()) {
  ------------------
  |  Branch (989:9): [True: 1.51M, False: 0]
  ------------------
  990|  1.51M|        visit1_fn(0, m_graph_index, m_feerate);
  991|  1.51M|        visit2_fn(0, m_graph_index, SetType{});
  992|  1.51M|        m_graph_index = NO_GRAPH_INDEX;
  993|  1.51M|    }
  994|  1.51M|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl8GetLevelERKNS_11TxGraphImplE:
 1014|  4.37M|{
 1015|       |    // GetLevel() does not work for empty Clusters.
 1016|  4.37M|    if (!Assume(GetTxCount())) return -1;
  ------------------
  |  |  128|  4.37M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  |  Branch (1016:9): [True: 0, False: 4.37M]
  ------------------
 1017|       |
 1018|       |    // Get the Entry in this Cluster.
 1019|  4.37M|    const auto& entry = graph.m_entries[m_graph_index];
 1020|       |    // See if there is a level whose Locator matches this Cluster, if so return that level.
 1021|  4.52M|    for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (1021:25): [True: 4.52M, False: 0]
  ------------------
 1022|  4.52M|        if (entry.m_locator[level].cluster == this) return level;
  ------------------
  |  Branch (1022:13): [True: 4.37M, False: 145k]
  ------------------
 1023|  4.52M|    }
 1024|       |    // Given that we started with an Entry that occurs in this Cluster, one of its Locators must
 1025|       |    // point back to it.
 1026|  4.37M|    assert(false);
  ------------------
  |  Branch (1026:5): [Folded, False: 0]
  ------------------
 1027|      0|    return -1;
 1028|      0|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl13UpdateMappingEjj:
  344|  33.4k|    void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept final { Assume(cluster_idx == 0); m_graph_index = graph_idx; }
  ------------------
  |  |  128|  33.4k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl7UpdatedERNS_11TxGraphImplEib:
 1149|  2.44M|{
 1150|       |    // Don't do anything if this is empty.
 1151|  2.44M|    if (GetTxCount() == 0) return;
  ------------------
  |  Branch (1151:9): [True: 4.73k, False: 2.44M]
  ------------------
 1152|       |
 1153|  2.44M|    auto& entry = graph.m_entries[m_graph_index];
 1154|       |    // Discard any potential ChunkData prior to modifying the Cluster (as that could
 1155|       |    // invalidate its ordering).
 1156|  2.44M|    if (level == 0 && !rename) graph.ClearChunkData(entry);
  ------------------
  |  Branch (1156:9): [True: 819k, False: 1.62M]
  |  Branch (1156:23): [True: 808k, False: 11.5k]
  ------------------
 1157|  2.44M|    entry.m_locator[level].SetPresent(this, 0);
 1158|       |    // If this is for the main graph (level = 0), compute its chunking and store its information in
 1159|       |    // the Entry's m_main_lin_index and m_main_chunk_feerate.
 1160|  2.44M|    if (level == 0 && (rename || IsAcceptable())) {
  ------------------
  |  Branch (1160:9): [True: 819k, False: 1.62M]
  |  Branch (1160:24): [True: 11.5k, False: 808k]
  |  Branch (1160:34): [True: 745k, False: 62.2k]
  ------------------
 1161|   757k|        entry.m_main_lin_index = 0;
 1162|   757k|        entry.m_main_chunk_feerate = m_feerate;
 1163|   757k|        entry.m_main_equal_feerate_chunk_prefix_size = m_feerate.size;
 1164|   757k|        entry.m_main_max_chunk_fallback = m_graph_index;
 1165|       |        // Always use the special LinearizationIndex(-1), indicating singleton chunk at end of
 1166|       |        // Cluster, here.
 1167|   757k|        if (!rename) graph.CreateChunkData(m_graph_index, LinearizationIndex(-1));
  ------------------
  |  Branch (1167:13): [True: 745k, False: 11.5k]
  ------------------
 1168|   757k|    }
 1169|  2.44M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl14ClearChunkDataERNS0_5EntryE:
  882|  5.19M|{
  883|  5.19M|    if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
  ------------------
  |  Branch (883:9): [True: 799k, False: 4.39M]
  ------------------
  884|   799k|        Assume(m_main_chunkindex_observers == 0);
  ------------------
  |  |  128|   799k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  885|       |        // If the Entry has a non-empty m_main_chunkindex_iterator, extract it, and move the handle
  886|       |        // to the cache of discarded chunkindex entries.
  887|   799k|        m_main_chunkindex_discarded.emplace_back(m_main_chunkindex.extract(entry.m_main_chunkindex_iterator));
  888|   799k|        entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
  889|   799k|    }
  890|  5.19M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl7Locator10SetPresentEPNS_7ClusterEj:
  592|  10.5M|        void SetPresent(Cluster* c, DepGraphIndex i) noexcept { cluster = c; index = i; }
txgraph.cpp:_ZNK12_GLOBAL__N_17Cluster12IsAcceptableEv:
  135|   169M|    {
  136|   169M|        return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL;
  ------------------
  |  Branch (136:16): [True: 50.8M, False: 118M]
  |  Branch (136:57): [True: 118M, False: 778k]
  ------------------
  137|   169M|    }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl15CreateChunkDataEjj:
  893|  1.25M|{
  894|  1.25M|    auto& entry = m_entries[idx];
  895|       |    // Make sure to not create chunk data for unlinked entries, which would make invoking
  896|       |    // m_fallback_order on them impossible.
  897|  1.25M|    Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|  1.25M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  898|  1.25M|    if (!m_main_chunkindex_discarded.empty()) {
  ------------------
  |  Branch (898:9): [True: 114k, False: 1.13M]
  ------------------
  899|       |        // Reuse an discarded node handle.
  900|   114k|        auto& node = m_main_chunkindex_discarded.back().value();
  901|   114k|        node.m_graph_index = idx;
  902|   114k|        node.m_chunk_count = chunk_count;
  903|   114k|        auto insert_result = m_main_chunkindex.insert(std::move(m_main_chunkindex_discarded.back()));
  904|   114k|        Assume(insert_result.inserted);
  ------------------
  |  |  128|   114k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  905|   114k|        entry.m_main_chunkindex_iterator = insert_result.position;
  906|   114k|        m_main_chunkindex_discarded.pop_back();
  907|  1.13M|    } else {
  908|       |        // Construct a new entry.
  909|  1.13M|        auto emplace_result = m_main_chunkindex.emplace(idx, chunk_count);
  910|  1.13M|        Assume(emplace_result.second);
  ------------------
  |  |  128|  1.13M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  911|  1.13M|        entry.m_main_chunkindex_iterator = emplace_result.first;
  912|  1.13M|    }
  913|  1.25M|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl10ChunkOrderclERKNS0_9ChunkDataES4_:
  534|  9.93M|        {
  535|  9.93M|            return m_graph->CompareMainTransactions(a.m_graph_index, b.m_graph_index) < 0;
  536|  9.93M|        }
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl23CompareMainTransactionsEjj:
  494|  82.9M|    {
  495|  82.9M|        if (a == b) return std::strong_ordering::equal;
  ------------------
  |  Branch (495:13): [True: 2.02M, False: 80.9M]
  ------------------
  496|  80.9M|        Assume(a < m_entries.size() && b < m_entries.size());
  ------------------
  |  |  128|   161M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 80.9M, False: 0]
  |  |  |  Branch (128:51): [True: 80.9M, False: 0]
  |  |  ------------------
  ------------------
  497|  80.9M|        const auto& entry_a = m_entries[a];
  498|  80.9M|        const auto& entry_b = m_entries[b];
  499|       |        // Compare chunk feerates, and return result if it differs.
  500|  80.9M|        auto feerate_cmp = ByRatio{entry_b.m_main_chunk_feerate} <=> ByRatio{entry_a.m_main_chunk_feerate};
  501|  80.9M|        if (feerate_cmp != 0) return feerate_cmp;
  ------------------
  |  Branch (501:13): [True: 35.8M, False: 45.1M]
  ------------------
  502|       |        // Compare equal-feerate chunk prefix size for comparing equal chunk feerates. This does two
  503|       |        // things: it distinguishes equal-feerate chunks within the same cluster (because later
  504|       |        // ones will always have a higher prefix size), and it may distinguish equal-feerate chunks
  505|       |        // from distinct clusters.
  506|  45.1M|        if (entry_a.m_main_equal_feerate_chunk_prefix_size != entry_b.m_main_equal_feerate_chunk_prefix_size) {
  ------------------
  |  Branch (506:13): [True: 16.2M, False: 28.8M]
  ------------------
  507|  16.2M|            return entry_a.m_main_equal_feerate_chunk_prefix_size <=> entry_b.m_main_equal_feerate_chunk_prefix_size;
  508|  16.2M|        }
  509|       |        // Compare by maximum m_fallback_order element to order equal-feerate chunks in distinct
  510|       |        // clusters, when the equal-feerate-prefix size is also the same.
  511|  28.8M|        const auto& locator_a = entry_a.m_locator[0];
  512|  28.8M|        const auto& locator_b = entry_b.m_locator[0];
  513|  28.8M|        Assume(locator_a.IsPresent() && locator_b.IsPresent());
  ------------------
  |  |  128|  57.7M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 28.8M, False: 0]
  |  |  |  Branch (128:51): [True: 28.8M, False: 0]
  |  |  ------------------
  ------------------
  514|  28.8M|        if (locator_a.cluster != locator_b.cluster) {
  ------------------
  |  Branch (514:13): [True: 26.1M, False: 2.78M]
  ------------------
  515|  26.1M|            auto fallback_cmp = m_fallback_order(*m_entries[entry_a.m_main_max_chunk_fallback].m_ref,
  516|  26.1M|                                                 *m_entries[entry_b.m_main_max_chunk_fallback].m_ref);
  517|  26.1M|            if (fallback_cmp != 0) return fallback_cmp;
  ------------------
  |  Branch (517:17): [True: 26.1M, False: 0]
  ------------------
  518|       |            // This shouldn't be reachable as m_fallback_order defines a strong ordering.
  519|      0|            Assume(false);
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  520|      0|            return CompareClusters(locator_a.cluster, locator_b.cluster);
  521|  26.1M|        }
  522|       |        // Within a single chunk, sort by position within cluster linearization.
  523|  2.78M|        return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index;
  524|  28.8M|    }
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl7Locator9IsPresentEv:
  598|   215M|        bool IsPresent() const noexcept { return cluster != nullptr; }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl15CompareClustersEPNS_7ClusterES2_:
  482|   162M|    {
  483|       |        // The nullptr pointer compares before everything else.
  484|   162M|        if (a == nullptr || b == nullptr) {
  ------------------
  |  Branch (484:13): [True: 928k, False: 161M]
  |  Branch (484:29): [True: 188k, False: 161M]
  ------------------
  485|  1.11M|            return (a != nullptr) <=> (b != nullptr);
  486|  1.11M|        }
  487|       |        // If neither pointer is nullptr, compare the Clusters' sequence numbers.
  488|   161M|        Assume(a == b || a->m_sequence != b->m_sequence);
  ------------------
  |  |  128|   272M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 50.1M, False: 111M]
  |  |  |  Branch (128:51): [True: 111M, False: 0]
  |  |  ------------------
  ------------------
  489|   161M|        return a->m_sequence <=> b->m_sequence;
  490|   162M|    }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl9ChunkDataC2Ejj:
  477|  1.13M|            m_graph_index{graph_index}, m_chunk_count{chunk_count} {}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl15RemoveChunkDataERNS_11TxGraphImplE:
 1066|  78.0k|{
 1067|  78.0k|    if (GetTxCount() == 0) return;
  ------------------
  |  Branch (1067:9): [True: 0, False: 78.0k]
  ------------------
 1068|  78.0k|    auto& entry = graph.m_entries[m_graph_index];
 1069|  78.0k|    graph.ClearChunkData(entry);
 1070|  78.0k|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl13CopyToStagingERNS_11TxGraphImplE:
 1240|  1.47M|{
 1241|       |    // Construct an empty Cluster.
 1242|  1.47M|    auto ret = graph.CreateEmptySingletonCluster();
 1243|  1.47M|    auto ptr = ret.get();
 1244|       |    // Copy data.
 1245|  1.47M|    ptr->m_graph_index = m_graph_index;
 1246|  1.47M|    ptr->m_feerate = m_feerate;
 1247|       |    // Insert the new Cluster into the graph.
 1248|  1.47M|    graph.InsertCluster(/*level=*/1, std::move(ret), m_quality);
 1249|       |    // Update its Locators.
 1250|  1.47M|    ptr->Updated(graph, /*level=*/1, /*rename=*/false);
 1251|       |    // Update memory usage.
 1252|  1.47M|    graph.GetClusterSet(/*level=*/1).m_cluster_usage += ptr->TotalMemoryUsage();
 1253|  1.47M|    return ptr;
 1254|  1.47M|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl12GetConflictsERKNS_11TxGraphImplERNSt3__16vectorIPNS_7ClusterENS4_9allocatorIS7_EEEE:
 1184|   187k|{
 1185|       |    // Empty clusters have no conflicts.
 1186|   187k|    if (GetTxCount() == 0) return;
  ------------------
  |  Branch (1186:9): [True: 4.73k, False: 182k]
  ------------------
 1187|       |
 1188|   182k|    auto& entry = graph.m_entries[m_graph_index];
 1189|       |    // If the transaction in this Cluster also exists in a lower-level Cluster, then that Cluster
 1190|       |    // conflicts.
 1191|   182k|    if (entry.m_locator[0].IsPresent()) {
  ------------------
  |  Branch (1191:9): [True: 6.21k, False: 176k]
  ------------------
 1192|  6.21k|        out.push_back(entry.m_locator[0].cluster);
 1193|  6.21k|    }
 1194|   182k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl30MakeStagingTransactionsMissingERNS_11TxGraphImplE:
 2421|  44.1k|{
 2422|  44.1k|    if (GetTxCount()) {
  ------------------
  |  Branch (2422:9): [True: 39.7k, False: 4.40k]
  ------------------
 2423|  39.7k|        auto& entry = graph.m_entries[m_graph_index];
 2424|  39.7k|        entry.m_locator[1].SetMissing();
 2425|  39.7k|    }
 2426|  44.1k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl7Locator10SetMissingEv:
  588|  1.66M|        void SetMissing() noexcept { cluster = nullptr; index = 0; }
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl5ClearERNS_11TxGraphImplEi:
 1334|  18.5k|{
 1335|  18.5k|    Assume(GetTxCount());
  ------------------
  |  |  128|  18.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1336|  18.5k|    graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
 1337|  18.5k|    graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
 1338|  18.5k|    m_graph_index = NO_GRAPH_INDEX;
 1339|  18.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl12ClearLocatorEijb:
 1031|   617k|{
 1032|   617k|    auto& entry = m_entries[idx];
 1033|   617k|    auto& clusterset = GetClusterSet(level);
 1034|   617k|    Assume(entry.m_locator[level].IsPresent());
  ------------------
  |  |  128|   617k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1035|       |    // Change the locator from Present to Missing or Removed.
 1036|   617k|    if (level == 0 || !entry.m_locator[level - 1].IsPresent()) {
  ------------------
  |  Branch (1036:9): [True: 145k, False: 472k]
  |  Branch (1036:23): [True: 26.4k, False: 446k]
  ------------------
 1037|   171k|        entry.m_locator[level].SetMissing();
 1038|   446k|    } else {
 1039|   446k|        entry.m_locator[level].SetRemoved();
 1040|   446k|        clusterset.m_removed.push_back(idx);
 1041|   446k|    }
 1042|       |    // Update the transaction count.
 1043|   617k|    --clusterset.m_txcount;
 1044|   617k|    clusterset.m_txcount_oversized -= oversized_tx;
 1045|       |    // If clearing main, adjust the status of Locators of this transaction in staging, if it exists.
 1046|   617k|    if (level == 0 && GetTopLevel() == 1) {
  ------------------
  |  Branch (1046:9): [True: 145k, False: 472k]
  |  Branch (1046:23): [True: 31.3k, False: 113k]
  ------------------
 1047|  31.3k|        if (entry.m_locator[1].IsRemoved()) {
  ------------------
  |  Branch (1047:13): [True: 11.1k, False: 20.1k]
  ------------------
 1048|  11.1k|            entry.m_locator[1].SetMissing();
 1049|  20.1k|        } else if (!entry.m_locator[1].IsPresent()) {
  ------------------
  |  Branch (1049:20): [True: 5.86k, False: 14.3k]
  ------------------
 1050|  5.86k|            --m_staging_clusterset->m_txcount;
 1051|  5.86k|            m_staging_clusterset->m_txcount_oversized -= oversized_tx;
 1052|  5.86k|        }
 1053|  31.3k|    }
 1054|   617k|    if (level == 0) ClearChunkData(entry);
  ------------------
  |  Branch (1054:9): [True: 145k, False: 472k]
  ------------------
 1055|   617k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl7Locator10SetRemovedEv:
  590|   446k|        void SetRemoved() noexcept { cluster = nullptr; index = DepGraphIndex(-1); }
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl7Locator9IsRemovedEv:
  596|  72.4M|        bool IsRemoved() const noexcept { return cluster == nullptr && index == DepGraphIndex(-1); }
  ------------------
  |  Branch (596:50): [True: 4.34M, False: 68.1M]
  |  Branch (596:72): [True: 1.39M, False: 2.94M]
  ------------------
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl10MoveToMainERNS_11TxGraphImplE:
 1359|  20.2k|{
 1360|  20.2k|    if (GetTxCount()) {
  ------------------
  |  Branch (1360:9): [True: 15.5k, False: 4.73k]
  ------------------
 1361|  15.5k|        auto& entry = graph.m_entries[m_graph_index];
 1362|  15.5k|        entry.m_locator[1].SetMissing();
 1363|  15.5k|    }
 1364|  20.2k|    auto quality = m_quality;
 1365|  20.2k|    graph.GetClusterSet(/*level=*/1).m_cluster_usage -= TotalMemoryUsage();
 1366|  20.2k|    auto cluster = graph.ExtractCluster(/*level=*/1, quality, m_setindex);
 1367|  20.2k|    graph.InsertCluster(/*level=*/0, std::move(cluster), quality);
 1368|  20.2k|    graph.GetClusterSet(/*level=*/0).m_cluster_usage += TotalMemoryUsage();
 1369|  20.2k|    Updated(graph, /*level=*/0, /*rename=*/false);
 1370|  20.2k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl14ExtractClusterEiNS_12QualityLevelEj:
 1613|  2.95M|{
 1614|  2.95M|    Assume(quality != QualityLevel::NONE);
  ------------------
  |  |  128|  2.95M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1615|       |
 1616|  2.95M|    auto& clusterset = GetClusterSet(level);
 1617|  2.95M|    auto& quality_clusters = clusterset.m_clusters[int(quality)];
 1618|  2.95M|    Assume(setindex < quality_clusters.size());
  ------------------
  |  |  128|  2.95M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1619|       |
 1620|       |    // Extract the Cluster-owning unique_ptr.
 1621|  2.95M|    std::unique_ptr<Cluster> ret = std::move(quality_clusters[setindex]);
 1622|  2.95M|    ret->m_quality = QualityLevel::NONE;
 1623|  2.95M|    ret->m_setindex = ClusterSetIndex(-1);
 1624|       |
 1625|       |    // Clean up space in quality_cluster.
 1626|  2.95M|    auto max_setindex = quality_clusters.size() - 1;
 1627|  2.95M|    if (setindex != max_setindex) {
  ------------------
  |  Branch (1627:9): [True: 1.32M, False: 1.62M]
  ------------------
 1628|       |        // If the cluster was not the last element of quality_clusters, move that to take its place.
 1629|  1.32M|        quality_clusters.back()->m_setindex = setindex;
 1630|  1.32M|        quality_clusters[setindex] = std::move(quality_clusters.back());
 1631|  1.32M|    }
 1632|       |    // The last element of quality_clusters is now unused; drop it.
 1633|  2.95M|    quality_clusters.pop_back();
 1634|       |
 1635|  2.95M|    return ret;
 1636|  2.95M|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl7CompactEv:
 1380|  37.1k|{
 1381|       |    // Nothing to compact; SingletonClusterImpl is constant size.
 1382|  37.1k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl13ApplyRemovalsERNS_11TxGraphImplEiRNSt3__14spanIjLm18446744073709551615EEE:
 1303|   401k|{
 1304|       |    // We can only remove the one transaction this Cluster has.
 1305|   401k|    Assume(!to_remove.empty());
  ------------------
  |  |  128|   401k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1306|   401k|    Assume(GetTxCount());
  ------------------
  |  |  128|   401k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1307|   401k|    Assume(to_remove.front() == m_graph_index);
  ------------------
  |  |  128|   401k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1308|       |    // Pop all copies of m_graph_index from the front of to_remove (at least one, but there may be
 1309|       |    // multiple).
 1310|   419k|    do {
 1311|   419k|        to_remove = to_remove.subspan(1);
 1312|   419k|    } while (!to_remove.empty() && to_remove.front() == m_graph_index);
  ------------------
  |  Branch (1312:14): [True: 344k, False: 74.2k]
  |  Branch (1312:36): [True: 17.3k, False: 327k]
  ------------------
 1313|       |    // Clear this cluster.
 1314|   401k|    graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
 1315|   401k|    m_graph_index = NO_GRAPH_INDEX;
 1316|   401k|    graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
 1317|       |    // No need to account for m_cluster_usage changes here, as SingletonClusterImpl has constant
 1318|       |    // memory usage.
 1319|   401k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17SetClusterQualityEiNS_12QualityLevelEjS1_:
 1656|   921k|{
 1657|   921k|    Assume(new_quality != QualityLevel::NONE);
  ------------------
  |  |  128|   921k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1658|       |
 1659|       |    // Don't do anything if the quality did not change.
 1660|   921k|    if (old_quality == new_quality) return;
  ------------------
  |  Branch (1660:9): [True: 1.65k, False: 920k]
  ------------------
 1661|       |    // Extract the cluster from where it currently resides.
 1662|   920k|    auto cluster_ptr = ExtractCluster(level, old_quality, old_index);
 1663|       |    // And re-insert it where it belongs.
 1664|   920k|    InsertCluster(level, std::move(cluster_ptr), new_quality);
 1665|   920k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl5SplitERNS_11TxGraphImplEi:
 1514|   388k|{
 1515|   388k|    Assume(NeedsSplitting());
  ------------------
  |  |  128|   388k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1516|   388k|    Assume(!GetTxCount());
  ------------------
  |  |  128|   388k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1517|   388k|    graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
 1518|   388k|    return true;
 1519|   388k|}
txgraph.cpp:_ZNK12_GLOBAL__N_17Cluster14NeedsSplittingEv:
  154|   169M|    {
  155|   169M|        return m_quality == QualityLevel::NEEDS_SPLIT || m_quality == QualityLevel::NEEDS_SPLIT_FIX;
  ------------------
  |  Branch (155:16): [True: 495k, False: 168M]
  |  Branch (155:58): [True: 744, False: 168M]
  ------------------
  156|   169M|    }
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl19AppendChunkFeeratesERNSt3__16vectorI7FeeFracNS1_9allocatorIS3_EEEE:
 1392|  1.27M|{
 1393|  1.27M|    if (GetTxCount()) {
  ------------------
  |  Branch (1393:9): [True: 1.27M, False: 0]
  ------------------
 1394|  1.27M|        ret.push_back(m_feerate);
 1395|  1.27M|    }
 1396|  1.27M|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl14AppendTrimDataERNSt3__16vectorINS_10TrimTxDataENS1_9allocatorIS3_EEEERNS2_INS1_4pairIjjEENS4_IS9_EEEE:
 1430|  2.37M|{
 1431|  2.37M|    if (!GetTxCount()) return 0;
  ------------------
  |  Branch (1431:9): [True: 0, False: 2.37M]
  ------------------
 1432|  2.37M|    auto& entry = ret.emplace_back();
 1433|  2.37M|    entry.m_chunk_feerate = m_feerate;
 1434|  2.37M|    entry.m_index = m_graph_index;
 1435|  2.37M|    entry.m_tx_size = m_feerate.size;
 1436|  2.37M|    return m_feerate.size;
 1437|  2.37M|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl15GetAncestorRefsERKNS_11TxGraphImplERNSt3__14spanINS4_4pairIPNS_7ClusterEjEELm18446744073709551615EEERNS4_6vectorIPN7TxGraph3RefENS4_9allocatorISF_EEEE:
 2336|   933k|{
 2337|   933k|    Assume(GetTxCount());
  ------------------
  |  |  128|   933k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2338|  1.89M|    while (!args.empty()) {
  ------------------
  |  Branch (2338:12): [True: 987k, False: 909k]
  ------------------
 2339|   987k|        if (args.front().first != this) break;
  ------------------
  |  Branch (2339:13): [True: 24.8k, False: 962k]
  ------------------
 2340|   962k|        args = args.subspan(1);
 2341|   962k|    }
 2342|   933k|    const auto& entry = graph.m_entries[m_graph_index];
 2343|   933k|    Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|   933k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2344|   933k|    output.push_back(entry.m_ref);
 2345|   933k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl17GetDescendantRefsERKNS_11TxGraphImplERNSt3__14spanINS4_4pairIPNS_7ClusterEjEELm18446744073709551615EEERNS4_6vectorIPN7TxGraph3RefENS4_9allocatorISF_EEEE:
 2367|   467k|{
 2368|       |    // In a singleton cluster, the ancestors or descendants are always just the entire cluster.
 2369|   467k|    GetAncestorRefs(graph, args, output);
 2370|   467k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl14GetClusterRefsERNS_11TxGraphImplENSt3__14spanIPN7TxGraph3RefELm18446744073709551615EEEj:
 2387|   783k|{
 2388|   783k|    Assume(!range.empty());
  ------------------
  |  |  128|   783k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2389|   783k|    Assume(GetTxCount());
  ------------------
  |  |  128|   783k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2390|   783k|    Assume(start_pos == 0);
  ------------------
  |  |  128|   783k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2391|   783k|    const auto& entry = graph.m_entries[m_graph_index];
 2392|   783k|    Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|   783k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2393|   783k|    range[0] = entry.m_ref;
 2394|   783k|    return true;
 2395|   783k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl20GetIndividualFeerateEj:
 2403|   810k|{
 2404|   810k|    Assume(GetTxCount());
  ------------------
  |  |  128|   810k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2405|   810k|    Assume(idx == 0);
  ------------------
  |  |  128|   810k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2406|   810k|    return m_feerate;
 2407|   810k|}
txgraph.cpp:_ZN12_GLOBAL__N_120SingletonClusterImpl6SetFeeERNS_11TxGraphImplEijl:
 2739|  6.95k|{
 2740|  6.95k|    Assume(GetTxCount());
  ------------------
  |  |  128|  6.95k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2741|  6.95k|    Assume(idx == 0);
  ------------------
  |  |  128|  6.95k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2742|  6.95k|    m_feerate.fee = fee;
 2743|  6.95k|    Updated(graph, /*level=*/level, /*rename=*/false);
 2744|  6.95k|}
txgraph.cpp:_ZNK12_GLOBAL__N_120SingletonClusterImpl11SanityCheckERKNS_11TxGraphImplEi:
 2910|   784k|{
 2911|       |    // All singletons are optimal, oversized, or need splitting.
 2912|   784k|    Assume(IsOptimal() || IsOversized() || NeedsSplitting());
  ------------------
  |  |  128|  1.64M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 719k, False: 64.6k]
  |  |  |  Branch (128:51): [True: 50.2k, False: 14.3k]
  |  |  |  Branch (128:51): [True: 14.3k, False: 0]
  |  |  ------------------
  ------------------
 2913|   784k|    if (GetTxCount()) {
  ------------------
  |  Branch (2913:9): [True: 769k, False: 14.3k]
  ------------------
 2914|   769k|        const auto& entry = graph.m_entries[m_graph_index];
 2915|       |        // Check that the Entry has a locator pointing back to this Cluster & position within it.
 2916|   769k|        assert(entry.m_locator[level].cluster == this);
  ------------------
  |  Branch (2916:9): [True: 769k, False: 0]
  ------------------
 2917|   769k|        assert(entry.m_locator[level].index == 0);
  ------------------
  |  Branch (2917:9): [True: 769k, False: 0]
  ------------------
 2918|       |        // For main-level entries, check linearization position and chunk feerate.
 2919|   769k|        if (level == 0 && IsAcceptable()) {
  ------------------
  |  Branch (2919:13): [True: 705k, False: 64.8k]
  |  Branch (2919:27): [True: 658k, False: 46.2k]
  ------------------
 2920|   658k|            assert(entry.m_main_lin_index == 0);
  ------------------
  |  Branch (2920:13): [True: 658k, False: 0]
  ------------------
 2921|   658k|            assert(entry.m_main_chunk_feerate == m_feerate);
  ------------------
  |  Branch (2921:13): [True: 658k, False: 0]
  ------------------
 2922|   658k|            assert(entry.m_main_equal_feerate_chunk_prefix_size == m_feerate.size);
  ------------------
  |  Branch (2922:13): [True: 658k, False: 0]
  ------------------
 2923|   658k|            if (graph.m_main_clusterset.m_to_remove.empty()) {
  ------------------
  |  Branch (2923:17): [True: 622k, False: 36.5k]
  ------------------
 2924|   622k|                assert(entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end());
  ------------------
  |  Branch (2924:17): [True: 622k, False: 0]
  ------------------
 2925|   622k|                auto& chunk_data = *entry.m_main_chunkindex_iterator;
 2926|       |                assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
  ------------------
  |  Branch (2926:17): [True: 622k, False: 0]
  ------------------
 2927|   622k|            }
 2928|   658k|        }
 2929|   769k|    }
 2930|   784k|}
txgraph.cpp:_ZNK12_GLOBAL__N_17Cluster9IsOptimalEv:
  145|  1.00M|    {
  146|  1.00M|        return m_quality == QualityLevel::OPTIMAL;
  147|  1.00M|    }
txgraph.cpp:_ZNK12_GLOBAL__N_17Cluster11IsOversizedEv:
  151|  2.36M|    bool IsOversized() const noexcept { return m_quality == QualityLevel::OVERSIZED_SINGLETON; }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl25CreateEmptyGenericClusterEv:
  697|   215k|    {
  698|   215k|        return std::make_unique<GenericClusterImpl>(m_next_sequence_counter++);
  699|   215k|    }
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImplC2Em:
 2228|   215k|GenericClusterImpl::GenericClusterImpl(uint64_t sequence) noexcept : Cluster{sequence} {}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl21GetMinIntendedTxCountEv:
  277|  50.7k|    constexpr DepGraphIndex GetMinIntendedTxCount() const noexcept final { return MIN_INTENDED_TX_COUNT; }
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl13GetMaxTxCountEv:
  278|   106k|    constexpr DepGraphIndex GetMaxTxCount() const noexcept final { return MAX_TX_COUNT; }
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl16TotalMemoryUsageEv:
  916|   623k|{
  917|   623k|    return // Dynamic memory allocated in this Cluster.
  918|   623k|           memusage::DynamicUsage(m_mapping) + memusage::DynamicUsage(m_linearization) +
  919|       |           // Dynamic memory usage inside m_depgraph.
  920|   623k|           m_depgraph.DynamicMemoryUsage() +
  921|       |           // Memory usage of the allocated Cluster itself.
  922|   623k|           memusage::MallocUsage(sizeof(GenericClusterImpl)) +
  923|       |           // Memory usage of the ClusterSet::m_clusters entry.
  924|   623k|           sizeof(std::unique_ptr<Cluster>);
  925|   623k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl21GetDepGraphIndexRangeEv:
  279|  14.8k|    DepGraphIndex GetDepGraphIndexRange() const noexcept final { return m_depgraph.PositionRange(); }
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl10GetTxCountEv:
  280|  1.42M|    LinearizationIndex GetTxCount() const noexcept final { return m_linearization.size(); }
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl14GetTotalTxSizeEv:
  936|   297k|{
  937|   297k|    uint64_t ret{0};
  938|  3.04M|    for (auto i : m_linearization) {
  ------------------
  |  Branch (938:17): [True: 3.04M, False: 297k]
  ------------------
  939|  3.04M|        ret += m_depgraph.FeeRate(i).size;
  940|  3.04M|    }
  941|   297k|    return ret;
  942|   297k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl15GetClusterEntryEj:
  282|   603k|    GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { return m_mapping[index]; }
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl17AppendTransactionEj10FeePerUnitI9WeightTagE:
  945|   654k|{
  946|   654k|    Assume(graph_idx != GraphIndex(-1));
  ------------------
  |  |  128|   654k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  947|   654k|    auto ret = m_depgraph.AddTransaction(feerate);
  948|   654k|    m_mapping.push_back(graph_idx);
  949|   654k|    m_linearization.push_back(ret);
  950|   654k|    return ret;
  951|   654k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl15AddDependenciesEN13bitset_detail9IntBitSetImEEj:
  962|   654k|{
  963|   654k|    m_depgraph.AddDependencies(parents, child);
  964|   654k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl19ExtractTransactionsERKNSt3__18functionIFvjj10FeePerUnitI9WeightTagEEEERKNS2_IFvjjN13bitset_detail9IntBitSetImEEEEE:
  974|  14.8k|{
  975|  53.1k|    for (auto pos : m_linearization) {
  ------------------
  |  Branch (975:19): [True: 53.1k, False: 14.8k]
  ------------------
  976|  53.1k|        visit1_fn(pos, m_mapping[pos], FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(pos)));
  977|  53.1k|    }
  978|  53.1k|    for (auto pos : m_linearization) {
  ------------------
  |  Branch (978:19): [True: 53.1k, False: 14.8k]
  ------------------
  979|  53.1k|        visit2_fn(pos, m_mapping[pos], m_depgraph.GetReducedParents(pos));
  980|  53.1k|    }
  981|       |    // Purge this Cluster, now that everything has been moved.
  982|  14.8k|    m_depgraph = DepGraph<SetType>{};
  983|  14.8k|    m_linearization.clear();
  984|  14.8k|    m_mapping.clear();
  985|  14.8k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl8GetLevelERKNS_11TxGraphImplE:
  997|   279k|{
  998|       |    // GetLevel() does not work for empty Clusters.
  999|   279k|    if (!Assume(!m_linearization.empty())) return -1;
  ------------------
  |  |  128|   279k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  |  Branch (999:9): [True: 0, False: 279k]
  ------------------
 1000|       |
 1001|       |    // Pick an arbitrary Entry that occurs in this Cluster.
 1002|   279k|    const auto& entry = graph.m_entries[m_mapping[m_linearization.front()]];
 1003|       |    // See if there is a level whose Locator matches this Cluster, if so return that level.
 1004|   470k|    for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (1004:25): [True: 470k, False: 0]
  ------------------
 1005|   470k|        if (entry.m_locator[level].cluster == this) return level;
  ------------------
  |  Branch (1005:13): [True: 279k, False: 190k]
  ------------------
 1006|   470k|    }
 1007|       |    // Given that we started with an Entry that occurs in this Cluster, one of its Locators must
 1008|       |    // point back to it.
 1009|   279k|    assert(false);
  ------------------
  |  Branch (1009:5): [Folded, False: 0]
  ------------------
 1010|      0|    return -1;
 1011|      0|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl13UpdateMappingEjj:
  287|  3.22k|    void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept final { m_mapping[cluster_idx] = graph_idx; }
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl7UpdatedERNS_11TxGraphImplEib:
 1073|   584k|{
 1074|       |    // Update all the Locators for this Cluster's Entry objects.
 1075|  6.53M|    for (DepGraphIndex idx : m_linearization) {
  ------------------
  |  Branch (1075:28): [True: 6.53M, False: 584k]
  ------------------
 1076|  6.53M|        auto& entry = graph.m_entries[m_mapping[idx]];
 1077|       |        // Discard any potential ChunkData prior to modifying the Cluster (as that could
 1078|       |        // invalidate its ordering).
 1079|  6.53M|        if (level == 0 && !rename) graph.ClearChunkData(entry);
  ------------------
  |  Branch (1079:13): [True: 2.82M, False: 3.70M]
  |  Branch (1079:27): [True: 2.79M, False: 29.9k]
  ------------------
 1080|  6.53M|        entry.m_locator[level].SetPresent(this, idx);
 1081|  6.53M|    }
 1082|       |    // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
 1083|       |    // OPTIMAL, compute its chunking and store its information in the Entry's m_main_lin_index
 1084|       |    // and m_main_chunk_feerate. These fields are only accessed after making the entire graph
 1085|       |    // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
 1086|       |    // yet.
 1087|       |    // When rename=true, this is always performed for level 0, to make sure the values inside the
 1088|       |    // entries remain consistent with the chunk index (otherwise unrelated chunk index operations
 1089|       |    // could cause the index to become corrupted, by inserting elements in the wrong place).
 1090|   584k|    if (level == 0 && (rename || IsAcceptable())) {
  ------------------
  |  Branch (1090:9): [True: 124k, False: 460k]
  |  Branch (1090:24): [True: 1.22k, False: 123k]
  |  Branch (1090:34): [True: 49.6k, False: 73.8k]
  ------------------
 1091|  50.8k|        auto chunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
 1092|  50.8k|        LinearizationIndex lin_idx{0};
 1093|       |        /** The sum of all chunk feerate FeeFracs with the same feerate as the current chunk,
 1094|       |         *  up to and including the current chunk. */
 1095|  50.8k|        FeeFrac equal_feerate_chunk_feerate;
 1096|       |        // Iterate over the chunks.
 1097|   571k|        for (unsigned chunk_idx = 0; chunk_idx < chunking.size(); ++chunk_idx) {
  ------------------
  |  Branch (1097:38): [True: 520k, False: 50.8k]
  ------------------
 1098|   520k|            auto& chunk = chunking[chunk_idx];
 1099|   520k|            auto chunk_count = chunk.transactions.Count();
 1100|   520k|            Assume(chunk_count > 0);
  ------------------
  |  |  128|   520k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1101|       |            // Update equal_feerate_chunk_feerate to include this chunk, starting over when the
 1102|       |            // feerate changed.
 1103|   520k|            if (ByRatio{chunk.feerate} < ByRatio{equal_feerate_chunk_feerate}) {
  ------------------
  |  Branch (1103:17): [True: 129k, False: 391k]
  ------------------
 1104|   129k|                equal_feerate_chunk_feerate = chunk.feerate;
 1105|   391k|            } else {
 1106|       |                // Note that this is adding fees to fees, and sizes to sizes, so the overall
 1107|       |                // ratio remains the same; it's just accounting for the size of the added chunk.
 1108|   391k|                equal_feerate_chunk_feerate += chunk.feerate;
 1109|   391k|            }
 1110|       |            // Determine the m_fallback_order maximum transaction in the chunk.
 1111|   520k|            auto it = chunk.transactions.begin();
 1112|   520k|            GraphIndex max_element = m_mapping[*it];
 1113|   520k|            ++it;
 1114|   994k|            while (it != chunk.transactions.end()) {
  ------------------
  |  Branch (1114:20): [True: 473k, False: 520k]
  ------------------
 1115|   473k|                GraphIndex this_element = m_mapping[*it];
 1116|   473k|                if (graph.m_fallback_order(*graph.m_entries[this_element].m_ref, *graph.m_entries[max_element].m_ref) > 0) {
  ------------------
  |  Branch (1116:21): [True: 124k, False: 349k]
  ------------------
 1117|   124k|                    max_element = this_element;
 1118|   124k|                }
 1119|   473k|                ++it;
 1120|   473k|            }
 1121|       |            // Iterate over the transactions in the linearization, which must match those in chunk.
 1122|   994k|            while (true) {
  ------------------
  |  Branch (1122:20): [True: 994k, Folded]
  ------------------
 1123|   994k|                DepGraphIndex idx = m_linearization[lin_idx];
 1124|   994k|                GraphIndex graph_idx = m_mapping[idx];
 1125|   994k|                auto& entry = graph.m_entries[graph_idx];
 1126|   994k|                entry.m_main_lin_index = lin_idx++;
 1127|   994k|                entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
 1128|   994k|                entry.m_main_equal_feerate_chunk_prefix_size = equal_feerate_chunk_feerate.size;
 1129|   994k|                entry.m_main_max_chunk_fallback = max_element;
 1130|   994k|                Assume(chunk.transactions[idx]);
  ------------------
  |  |  128|   994k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1131|   994k|                chunk.transactions.Reset(idx);
 1132|   994k|                if (chunk.transactions.None()) {
  ------------------
  |  Branch (1132:21): [True: 520k, False: 473k]
  ------------------
 1133|       |                    // Last transaction in the chunk.
 1134|   520k|                    if (chunk_count == 1 && chunk_idx + 1 == chunking.size()) {
  ------------------
  |  Branch (1134:25): [True: 427k, False: 93.4k]
  |  Branch (1134:45): [True: 38.8k, False: 388k]
  ------------------
 1135|       |                        // If this is the final chunk of the cluster, and it contains just a single
 1136|       |                        // transaction (which will always be true for the very common singleton
 1137|       |                        // clusters), store the special value -1 as chunk count.
 1138|  38.8k|                        chunk_count = LinearizationIndex(-1);
 1139|  38.8k|                    }
 1140|   520k|                    if (!rename) graph.CreateChunkData(graph_idx, chunk_count);
  ------------------
  |  Branch (1140:25): [True: 505k, False: 14.8k]
  ------------------
 1141|   520k|                    break;
 1142|   520k|                }
 1143|   994k|            }
 1144|   520k|        }
 1145|  50.8k|    }
 1146|   584k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl15RemoveChunkDataERNS_11TxGraphImplE:
 1058|  58.5k|{
 1059|  1.06M|    for (DepGraphIndex idx : m_linearization) {
  ------------------
  |  Branch (1059:28): [True: 1.06M, False: 58.5k]
  ------------------
 1060|  1.06M|        auto& entry = graph.m_entries[m_mapping[idx]];
 1061|  1.06M|        graph.ClearChunkData(entry);
 1062|  1.06M|    }
 1063|  58.5k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl13CopyToStagingERNS_11TxGraphImplE:
 1222|  18.6k|{
 1223|       |    // Construct an empty Cluster.
 1224|  18.6k|    auto ret = graph.CreateEmptyGenericCluster();
 1225|  18.6k|    auto ptr = ret.get();
 1226|       |    // Copy depgraph, mapping, and linearization.
 1227|  18.6k|    ptr->m_depgraph = m_depgraph;
 1228|  18.6k|    ptr->m_mapping = m_mapping;
 1229|  18.6k|    ptr->m_linearization = m_linearization;
 1230|       |    // Insert the new Cluster into the graph.
 1231|  18.6k|    graph.InsertCluster(/*level=*/1, std::move(ret), m_quality);
 1232|       |    // Update its Locators.
 1233|  18.6k|    ptr->Updated(graph, /*level=*/1, /*rename=*/false);
 1234|       |    // Update memory usage.
 1235|  18.6k|    graph.GetClusterSet(/*level=*/1).m_cluster_usage += ptr->TotalMemoryUsage();
 1236|  18.6k|    return ptr;
 1237|  18.6k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl12GetConflictsERKNS_11TxGraphImplERNSt3__16vectorIPNS_7ClusterENS4_9allocatorIS7_EEEE:
 1172|  98.8k|{
 1173|  1.03M|    for (auto i : m_linearization) {
  ------------------
  |  Branch (1173:17): [True: 1.03M, False: 98.8k]
  ------------------
 1174|  1.03M|        auto& entry = graph.m_entries[m_mapping[i]];
 1175|       |        // For every transaction Entry in this Cluster, if it also exists in a lower-level Cluster,
 1176|       |        // then that Cluster conflicts.
 1177|  1.03M|        if (entry.m_locator[0].IsPresent()) {
  ------------------
  |  Branch (1177:13): [True: 1.00M, False: 21.2k]
  ------------------
 1178|  1.00M|            out.push_back(entry.m_locator[0].cluster);
 1179|  1.00M|        }
 1180|  1.03M|    }
 1181|  98.8k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl30MakeStagingTransactionsMissingERNS_11TxGraphImplE:
 2410|  95.1k|{
 2411|       |    // Mark all transactions of a Cluster missing, needed when aborting staging, so that the
 2412|       |    // corresponding Locators don't retain references into aborted Clusters.
 2413|  1.01M|    for (auto ci : m_linearization) {
  ------------------
  |  Branch (2413:18): [True: 1.01M, False: 95.1k]
  ------------------
 2414|  1.01M|        GraphIndex idx = m_mapping[ci];
 2415|  1.01M|        auto& entry = graph.m_entries[idx];
 2416|  1.01M|        entry.m_locator[1].SetMissing();
 2417|  1.01M|    }
 2418|  95.1k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl5ClearERNS_11TxGraphImplEi:
 1322|    457|{
 1323|    457|    Assume(GetTxCount());
  ------------------
  |  |  128|    457|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1324|    457|    graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
 1325|  3.66k|    for (auto i : m_linearization) {
  ------------------
  |  Branch (1325:17): [True: 3.66k, False: 457]
  ------------------
 1326|  3.66k|        graph.ClearLocator(level, m_mapping[i], m_quality == QualityLevel::OVERSIZED_SINGLETON);
 1327|  3.66k|    }
 1328|    457|    m_depgraph = {};
 1329|    457|    m_linearization.clear();
 1330|    457|    m_mapping.clear();
 1331|    457|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl10MoveToMainERNS_11TxGraphImplE:
 1342|  1.82k|{
 1343|  12.6k|    for (auto i : m_linearization) {
  ------------------
  |  Branch (1343:17): [True: 12.6k, False: 1.82k]
  ------------------
 1344|  12.6k|        GraphIndex idx = m_mapping[i];
 1345|  12.6k|        auto& entry = graph.m_entries[idx];
 1346|  12.6k|        entry.m_locator[1].SetMissing();
 1347|  12.6k|    }
 1348|  1.82k|    auto quality = m_quality;
 1349|       |    // Subtract memory usage from staging and add it to main.
 1350|  1.82k|    graph.GetClusterSet(/*level=*/1).m_cluster_usage -= TotalMemoryUsage();
 1351|  1.82k|    graph.GetClusterSet(/*level=*/0).m_cluster_usage += TotalMemoryUsage();
 1352|       |    // Remove cluster itself from staging and add it to main.
 1353|  1.82k|    auto cluster = graph.ExtractCluster(1, quality, m_setindex);
 1354|  1.82k|    graph.InsertCluster(/*level=*/0, std::move(cluster), quality);
 1355|  1.82k|    Updated(graph, /*level=*/0, /*rename=*/false);
 1356|  1.82k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl7CompactEv:
 1373|   330k|{
 1374|   330k|    m_linearization.shrink_to_fit();
 1375|   330k|    m_mapping.shrink_to_fit();
 1376|   330k|    m_depgraph.Compact();
 1377|   330k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl13ApplyRemovalsERNS_11TxGraphImplEiRNSt3__14spanIjLm18446744073709551615EEE:
 1257|  77.7k|{
 1258|       |    // Iterate over the prefix of to_remove that applies to this cluster.
 1259|  77.7k|    Assume(!to_remove.empty());
  ------------------
  |  |  128|  77.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1260|  77.7k|    SetType todo;
 1261|  77.7k|    graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
 1262|   245k|    do {
 1263|   245k|        GraphIndex idx = to_remove.front();
 1264|   245k|        Assume(idx < graph.m_entries.size());
  ------------------
  |  |  128|   245k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1265|   245k|        auto& entry = graph.m_entries[idx];
 1266|   245k|        auto& locator = entry.m_locator[level];
 1267|       |        // Stop once we hit an entry that applies to another Cluster.
 1268|   245k|        if (locator.cluster != this) break;
  ------------------
  |  Branch (1268:13): [True: 51.6k, False: 193k]
  ------------------
 1269|       |        // - Remember it in a set of to-remove DepGraphIndexes.
 1270|   193k|        todo.Set(locator.index);
 1271|       |        // - Remove from m_mapping. This isn't strictly necessary as unused positions in m_mapping
 1272|       |        //   are just never accessed, but set it to -1 here to increase the ability to detect a bug
 1273|       |        //   that causes it to be accessed regardless.
 1274|   193k|        m_mapping[locator.index] = GraphIndex(-1);
 1275|       |        // - Remove its linearization index from the Entry (if in main).
 1276|   193k|        if (level == 0) {
  ------------------
  |  Branch (1276:13): [True: 32.6k, False: 161k]
  ------------------
 1277|  32.6k|            entry.m_main_lin_index = LinearizationIndex(-1);
 1278|  32.6k|        }
 1279|       |        // - Mark it as missing/removed in the Entry's locator.
 1280|   193k|        graph.ClearLocator(level, idx, m_quality == QualityLevel::OVERSIZED_SINGLETON);
 1281|   193k|        to_remove = to_remove.subspan(1);
 1282|   193k|    } while(!to_remove.empty());
  ------------------
  |  Branch (1282:13): [True: 167k, False: 26.1k]
  ------------------
 1283|       |
 1284|  77.7k|    Assume(todo.Any());
  ------------------
  |  |  128|  77.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1285|       |    // Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
 1286|       |    // removed, so we benefit from batching all the removals).
 1287|  77.7k|    m_depgraph.RemoveTransactions(todo);
 1288|  77.7k|    m_mapping.resize(m_depgraph.PositionRange());
 1289|       |
 1290|       |    // Filter removed transactions out of m_linearization.
 1291|  77.7k|    m_linearization.erase(std::remove_if(m_linearization.begin(), m_linearization.end(),
 1292|  77.7k|                                         [&](auto pos) { return todo[pos]; }),
 1293|  77.7k|                          m_linearization.end());
 1294|       |
 1295|  77.7k|    Compact();
 1296|  77.7k|    graph.GetClusterSet(level).m_cluster_usage += TotalMemoryUsage();
 1297|  77.7k|    auto new_quality = IsTopological() ? QualityLevel::NEEDS_SPLIT : QualityLevel::NEEDS_SPLIT_FIX;
  ------------------
  |  Branch (1297:24): [True: 77.1k, False: 600]
  ------------------
 1298|  77.7k|    graph.SetClusterQuality(level, m_quality, m_setindex, new_quality);
 1299|  77.7k|    Updated(graph, /*level=*/level, /*rename=*/false);
 1300|  77.7k|}
txgraph.cpp:_ZZN12_GLOBAL__N_118GenericClusterImpl13ApplyRemovalsERNS_11TxGraphImplEiRNSt3__14spanIjLm18446744073709551615EEEENK3$_0clIjEEDaT_:
 1292|   943k|                                         [&](auto pos) { return todo[pos]; }),
txgraph.cpp:_ZNK12_GLOBAL__N_17Cluster13IsTopologicalEv:
  140|   988k|    {
  141|   988k|        return m_quality != QualityLevel::NEEDS_FIX && m_quality != QualityLevel::NEEDS_SPLIT_FIX;
  ------------------
  |  Branch (141:16): [True: 742k, False: 246k]
  |  Branch (141:56): [True: 741k, False: 871]
  ------------------
  142|   988k|    }
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl5SplitERNS_11TxGraphImplEi:
 1440|  77.2k|{
 1441|       |    // This function can only be called when the Cluster needs splitting.
 1442|  77.2k|    Assume(NeedsSplitting());
  ------------------
  |  |  128|  77.2k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1443|       |    // Determine the new quality the split-off Clusters will have.
 1444|  77.2k|    QualityLevel new_quality = IsTopological() ? QualityLevel::NEEDS_RELINEARIZE : QualityLevel::NEEDS_FIX;
  ------------------
  |  Branch (1444:32): [True: 76.7k, False: 480]
  ------------------
 1445|       |    /** Which positions are still left in this Cluster. */
 1446|  77.2k|    auto todo = m_depgraph.Positions();
 1447|       |    /** Mapping from transaction positions in this Cluster to the Cluster where it ends up, and
 1448|       |     *  its position therein. */
 1449|  77.2k|    std::vector<std::pair<Cluster*, DepGraphIndex>> remap(m_depgraph.PositionRange());
 1450|  77.2k|    std::vector<Cluster*> new_clusters;
 1451|  77.2k|    bool first{true};
 1452|       |    // Iterate over the connected components of this Cluster's m_depgraph.
 1453|   151k|    while (todo.Any()) {
  ------------------
  |  Branch (1453:12): [True: 79.7k, False: 71.5k]
  ------------------
 1454|  79.7k|        auto component = m_depgraph.FindConnectedComponent(todo);
 1455|  79.7k|        auto component_size = component.Count();
 1456|  79.7k|        auto split_quality = component_size <= 1 ? QualityLevel::OPTIMAL : new_quality;
  ------------------
  |  Branch (1456:30): [True: 37.1k, False: 42.6k]
  ------------------
 1457|  79.7k|        if (first && component == todo && SetType::Fill(component_size) == component && component_size >= MIN_INTENDED_TX_COUNT) {
  ------------------
  |  Branch (1457:13): [True: 62.4k, False: 17.3k]
  |  Branch (1457:13): [True: 5.75k, False: 74.0k]
  |  Branch (1457:22): [True: 50.5k, False: 11.9k]
  |  Branch (1457:43): [True: 14.7k, False: 35.7k]
  |  Branch (1457:89): [True: 5.75k, False: 8.99k]
  ------------------
 1458|       |            // The existing Cluster is an entire component, without holes. Leave it be, but update
 1459|       |            // its quality. If there are holes, we continue, so that the Cluster is reconstructed
 1460|       |            // without holes, reducing memory usage. If the component's size is below the intended
 1461|       |            // transaction count for this Cluster implementation, continue so that it can get
 1462|       |            // converted.
 1463|  5.75k|            Assume(todo == m_depgraph.Positions());
  ------------------
  |  |  128|  5.75k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1464|  5.75k|            graph.SetClusterQuality(level, m_quality, m_setindex, split_quality);
 1465|       |            // If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
 1466|       |            // chunking.
 1467|  5.75k|            Updated(graph, /*level=*/level, /*rename=*/false);
 1468|  5.75k|            return false;
 1469|  5.75k|        }
 1470|  74.0k|        first = false;
 1471|       |        // Construct a new Cluster to hold the found component.
 1472|  74.0k|        auto new_cluster = graph.CreateEmptyCluster(component_size);
 1473|  74.0k|        new_clusters.push_back(new_cluster.get());
 1474|       |        // Remember that all the component's transactions go to this new Cluster. The positions
 1475|       |        // will be determined below, so use -1 for now.
 1476|   692k|        for (auto i : component) {
  ------------------
  |  Branch (1476:21): [True: 692k, False: 74.0k]
  ------------------
 1477|   692k|            remap[i] = {new_cluster.get(), DepGraphIndex(-1)};
 1478|   692k|        }
 1479|  74.0k|        graph.InsertCluster(level, std::move(new_cluster), split_quality);
 1480|  74.0k|        todo -= component;
 1481|  74.0k|    }
 1482|       |    // We have to split the Cluster up. Remove accounting for the existing one first.
 1483|  71.5k|    graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
 1484|       |    // Redistribute the transactions.
 1485|   692k|    for (auto i : m_linearization) {
  ------------------
  |  Branch (1485:17): [True: 692k, False: 71.5k]
  ------------------
 1486|       |        /** The cluster which transaction originally in position i is moved to. */
 1487|   692k|        Cluster* new_cluster = remap[i].first;
 1488|       |        // Copy the transaction to the new cluster's depgraph, and remember the position.
 1489|   692k|        remap[i].second = new_cluster->AppendTransaction(m_mapping[i], FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(i)));
 1490|   692k|    }
 1491|       |    // Redistribute the dependencies.
 1492|   692k|    for (auto i : m_linearization) {
  ------------------
  |  Branch (1492:17): [True: 692k, False: 71.5k]
  ------------------
 1493|       |        /** The cluster transaction in position i is moved to. */
 1494|   692k|        Cluster* new_cluster = remap[i].first;
 1495|       |        // Copy its parents, translating positions.
 1496|   692k|        SetType new_parents;
 1497|   712k|        for (auto par : m_depgraph.GetReducedParents(i)) new_parents.Set(remap[par].second);
  ------------------
  |  Branch (1497:23): [True: 712k, False: 692k]
  ------------------
 1498|   692k|        new_cluster->AddDependencies(new_parents, remap[i].second);
 1499|   692k|    }
 1500|       |    // Update all the Locators of moved transactions, and memory usage.
 1501|  74.0k|    for (Cluster* new_cluster : new_clusters) {
  ------------------
  |  Branch (1501:31): [True: 74.0k, False: 71.5k]
  ------------------
 1502|  74.0k|        new_cluster->Updated(graph, /*level=*/level, /*rename=*/false);
 1503|  74.0k|        new_cluster->Compact();
 1504|  74.0k|        graph.GetClusterSet(level).m_cluster_usage += new_cluster->TotalMemoryUsage();
 1505|  74.0k|    }
 1506|       |    // Wipe this Cluster, and return that it needs to be deleted.
 1507|  71.5k|    m_depgraph = DepGraph<SetType>{};
 1508|  71.5k|    m_mapping.clear();
 1509|  71.5k|    m_linearization.clear();
 1510|  71.5k|    return true;
 1511|  77.2k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl5MergeERNS_11TxGraphImplEiRNS_7ClusterE:
 1522|  1.53M|{
 1523|       |    /** Vector to store the positions in this Cluster for each position in other. */
 1524|  1.53M|    std::vector<DepGraphIndex> remap(other.GetDepGraphIndexRange());
 1525|       |    // Iterate over all transactions in the other Cluster (the one being absorbed).
 1526|  1.53M|    other.ExtractTransactions([&](DepGraphIndex pos, GraphIndex idx, FeePerWeight feerate) noexcept {
 1527|       |        // Copy the transaction into this Cluster, and remember its position.
 1528|  1.53M|        auto new_pos = m_depgraph.AddTransaction(feerate);
 1529|       |        // Since this cluster must have been made hole-free before being merged into, all added
 1530|       |        // transactions should appear at the end.
 1531|  1.53M|        Assume(new_pos == m_mapping.size());
 1532|  1.53M|        remap[pos] = new_pos;
 1533|  1.53M|        m_mapping.push_back(idx);
 1534|  1.53M|        m_linearization.push_back(new_pos);
 1535|  1.53M|    }, [&](DepGraphIndex pos, GraphIndex idx, SetType other_parents) noexcept {
 1536|       |        // Copy the transaction's dependencies, translating them using remap.
 1537|  1.53M|        SetType parents;
 1538|  1.53M|        for (auto par : other_parents) {
 1539|  1.53M|            parents.Set(remap[par]);
 1540|  1.53M|        }
 1541|  1.53M|        m_depgraph.AddDependencies(parents, remap[pos]);
 1542|       |        // Update the transaction's Locator. There is no need to call Updated() to update chunk
 1543|       |        // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
 1544|       |        // merged Cluster later anyway.
 1545|  1.53M|        auto& entry = graph.m_entries[idx];
 1546|       |        // Discard any potential ChunkData prior to modifying the Cluster (as that could
 1547|       |        // invalidate its ordering).
 1548|  1.53M|        if (level == 0) graph.ClearChunkData(entry);
 1549|  1.53M|        entry.m_locator[level].SetPresent(this, remap[pos]);
 1550|  1.53M|    });
 1551|  1.53M|}
txgraph.cpp:_ZZN12_GLOBAL__N_118GenericClusterImpl5MergeERNS_11TxGraphImplEiRNS_7ClusterEENK3$_0clEjj10FeePerUnitI9WeightTagE:
 1526|  1.57M|    other.ExtractTransactions([&](DepGraphIndex pos, GraphIndex idx, FeePerWeight feerate) noexcept {
 1527|       |        // Copy the transaction into this Cluster, and remember its position.
 1528|  1.57M|        auto new_pos = m_depgraph.AddTransaction(feerate);
 1529|       |        // Since this cluster must have been made hole-free before being merged into, all added
 1530|       |        // transactions should appear at the end.
 1531|  1.57M|        Assume(new_pos == m_mapping.size());
  ------------------
  |  |  128|  1.57M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1532|  1.57M|        remap[pos] = new_pos;
 1533|  1.57M|        m_mapping.push_back(idx);
 1534|  1.57M|        m_linearization.push_back(new_pos);
 1535|  1.57M|    }, [&](DepGraphIndex pos, GraphIndex idx, SetType other_parents) noexcept {
txgraph.cpp:_ZZN12_GLOBAL__N_118GenericClusterImpl5MergeERNS_11TxGraphImplEiRNS_7ClusterEENK3$_1clEjjN13bitset_detail9IntBitSetImEE:
 1535|  1.57M|    }, [&](DepGraphIndex pos, GraphIndex idx, SetType other_parents) noexcept {
 1536|       |        // Copy the transaction's dependencies, translating them using remap.
 1537|  1.57M|        SetType parents;
 1538|  1.57M|        for (auto par : other_parents) {
  ------------------
  |  Branch (1538:23): [True: 39.9k, False: 1.57M]
  ------------------
 1539|  39.9k|            parents.Set(remap[par]);
 1540|  39.9k|        }
 1541|  1.57M|        m_depgraph.AddDependencies(parents, remap[pos]);
 1542|       |        // Update the transaction's Locator. There is no need to call Updated() to update chunk
 1543|       |        // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
 1544|       |        // merged Cluster later anyway.
 1545|  1.57M|        auto& entry = graph.m_entries[idx];
 1546|       |        // Discard any potential ChunkData prior to modifying the Cluster (as that could
 1547|       |        // invalidate its ordering).
 1548|  1.57M|        if (level == 0) graph.ClearChunkData(entry);
  ------------------
  |  Branch (1548:13): [True: 305k, False: 1.26M]
  ------------------
 1549|  1.57M|        entry.m_locator[level].SetPresent(this, remap[pos]);
 1550|  1.57M|    });
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl17ApplyDependenciesERNS_11TxGraphImplEiNSt3__14spanINS3_4pairIjjEELm18446744073709551615EEE:
 1560|   216k|{
 1561|       |    // Sort the list of dependencies to apply by child, so those can be applied in batch.
 1562|   216k|    std::ranges::sort(to_apply, [](auto& a, auto& b) { return a.second < b.second; });
 1563|       |    // Iterate over groups of to-be-added dependencies with the same child.
 1564|   216k|    auto it = to_apply.begin();
 1565|  1.37M|    while (it != to_apply.end()) {
  ------------------
  |  Branch (1565:12): [True: 1.15M, False: 216k]
  ------------------
 1566|  1.15M|        auto& first_child = graph.m_entries[it->second].m_locator[level];
 1567|  1.15M|        const auto child_idx = first_child.index;
 1568|       |        // Iterate over all to-be-added dependencies within that same child, gather the relevant
 1569|       |        // parents.
 1570|  1.15M|        SetType parents;
 1571|  3.76M|        while (it != to_apply.end()) {
  ------------------
  |  Branch (1571:16): [True: 3.54M, False: 216k]
  ------------------
 1572|  3.54M|            auto& child = graph.m_entries[it->second].m_locator[level];
 1573|  3.54M|            auto& parent = graph.m_entries[it->first].m_locator[level];
 1574|  3.54M|            Assume(child.cluster == this && parent.cluster == this);
  ------------------
  |  |  128|  7.09M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 3.54M, False: 0]
  |  |  |  Branch (128:51): [True: 3.54M, False: 0]
  |  |  ------------------
  ------------------
 1575|  3.54M|            if (child.index != child_idx) break;
  ------------------
  |  Branch (1575:17): [True: 937k, False: 2.61M]
  ------------------
 1576|  2.61M|            parents.Set(parent.index);
 1577|  2.61M|            ++it;
 1578|  2.61M|        }
 1579|       |        // Push all dependencies to the underlying DepGraph. Note that this is O(N) in the size of
 1580|       |        // the cluster, regardless of the number of parents being added, so batching them together
 1581|       |        // has a performance benefit.
 1582|  1.15M|        m_depgraph.AddDependencies(parents, child_idx);
 1583|  1.15M|    }
 1584|       |
 1585|       |    // Finally set the cluster to NEEDS_FIX, so its linearization is fixed the next time it is
 1586|       |    // attempted to be made ACCEPTABLE.
 1587|   216k|    Assume(!NeedsSplitting());
  ------------------
  |  |  128|   216k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1588|   216k|    Assume(!IsOversized());
  ------------------
  |  |  128|   216k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1589|   216k|    graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_FIX);
 1590|       |
 1591|       |    // Finally push the changes to graph.m_entries.
 1592|   216k|    Updated(graph, /*level=*/level, /*rename=*/false);
 1593|   216k|}
txgraph.cpp:_ZZN12_GLOBAL__N_118GenericClusterImpl17ApplyDependenciesERNS_11TxGraphImplEiNSt3__14spanINS3_4pairIjjEELm18446744073709551615EEEENK3$_0clIS6_S6_EEDaRT_RT0_:
 1562|   155M|    std::ranges::sort(to_apply, [](auto& a, auto& b) { return a.second < b.second; });
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS_11TxGraphImplEim:
 2158|   224k|{
 2159|       |    // We can only relinearize Clusters that do not need splitting.
 2160|   224k|    Assume(!NeedsSplitting());
  ------------------
  |  |  128|   224k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2161|       |    // No work is required for Clusters which are already optimally linearized.
 2162|   224k|    if (IsOptimal()) return {0, false};
  ------------------
  |  Branch (2162:9): [True: 0, False: 224k]
  ------------------
 2163|       |    // Invoke the actual linearization algorithm (passing in the existing one).
 2164|   224k|    uint64_t rng_seed = graph.m_rng.rand64();
 2165|   224k|    const auto fallback_order = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
 2166|   224k|        const auto ref_a = graph.m_entries[m_mapping[a]].m_ref;
 2167|   224k|        const auto ref_b = graph.m_entries[m_mapping[b]].m_ref;
 2168|   224k|        return graph.m_fallback_order(*ref_a, *ref_b);
 2169|   224k|    };
 2170|   224k|    auto [linearization, optimal, cost] = Linearize(
 2171|   224k|        /*depgraph=*/m_depgraph,
 2172|   224k|        /*max_cost=*/max_cost,
 2173|   224k|        /*rng_seed=*/rng_seed,
 2174|   224k|        /*fallback_order=*/fallback_order,
 2175|   224k|        /*old_linearization=*/m_linearization,
 2176|   224k|        /*is_topological=*/IsTopological());
 2177|       |    // Postlinearize to improve the linearization (if optimal, only the sub-chunk order).
 2178|       |    // This also guarantees that all chunks are connected (even when non-optimal).
 2179|   224k|    PostLinearize(m_depgraph, linearization);
 2180|       |    // Update the linearization.
 2181|   224k|    m_linearization = std::move(linearization);
 2182|       |    // Update the Cluster's quality.
 2183|   224k|    bool improved = false;
 2184|   224k|    if (optimal) {
  ------------------
  |  Branch (2184:9): [True: 75.6k, False: 148k]
  ------------------
 2185|  75.6k|        graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::OPTIMAL);
 2186|  75.6k|        improved = true;
 2187|   148k|    } else if (max_cost >= graph.m_acceptable_cost && !IsAcceptable()) {
  ------------------
  |  Branch (2187:16): [True: 145k, False: 3.24k]
  |  Branch (2187:55): [True: 142k, False: 2.69k]
  ------------------
 2188|   142k|        graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::ACCEPTABLE);
 2189|   142k|        improved = true;
 2190|   142k|    } else if (!IsTopological()) {
  ------------------
  |  Branch (2190:16): [True: 622, False: 5.32k]
  ------------------
 2191|    622|        graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
 2192|    622|        improved = true;
 2193|    622|    }
 2194|       |    // Update the Entry objects.
 2195|   224k|    Updated(graph, /*level=*/level, /*rename=*/false);
 2196|   224k|    return {cost, improved};
 2197|   224k|}
txgraph.cpp:_ZZN12_GLOBAL__N_118GenericClusterImpl11RelinearizeERNS_11TxGraphImplEimENK3$_0clEjj:
 2165|  4.46M|    const auto fallback_order = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
 2166|  4.46M|        const auto ref_a = graph.m_entries[m_mapping[a]].m_ref;
 2167|  4.46M|        const auto ref_b = graph.m_entries[m_mapping[b]].m_ref;
 2168|  4.46M|        return graph.m_fallback_order(*ref_a, *ref_b);
 2169|  4.46M|    };
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl19AppendChunkFeeratesERNSt3__16vectorI7FeeFracNS1_9allocatorIS3_EEEE:
 1385|   115k|{
 1386|   115k|    auto chunk_feerates = ChunkLinearization(m_depgraph, m_linearization);
 1387|   115k|    ret.reserve(ret.size() + chunk_feerates.size());
 1388|   115k|    ret.insert(ret.end(), chunk_feerates.begin(), chunk_feerates.end());
 1389|   115k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl14AppendTrimDataERNSt3__16vectorINS_10TrimTxDataENS1_9allocatorIS3_EEEERNS2_INS1_4pairIjjEENS4_IS9_EEEE:
 1399|   176k|{
 1400|   176k|    Assume(IsAcceptable());
  ------------------
  |  |  128|   176k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1401|   176k|    auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
 1402|   176k|    LinearizationIndex pos{0};
 1403|   176k|    uint64_t size{0};
 1404|   176k|    auto prev_index = GraphIndex(-1);
 1405|       |    // Iterate over the chunks of this cluster's linearization.
 1406|   962k|    for (const auto& [chunk, chunk_feerate] : linchunking) {
  ------------------
  |  Branch (1406:45): [True: 962k, False: 176k]
  ------------------
 1407|       |        // Iterate over the transactions of that chunk, in linearization order.
 1408|   962k|        auto chunk_tx_count = chunk.Count();
 1409|  2.58M|        for (unsigned j = 0; j < chunk_tx_count; ++j) {
  ------------------
  |  Branch (1409:30): [True: 1.62M, False: 962k]
  ------------------
 1410|  1.62M|            auto cluster_idx = m_linearization[pos];
 1411|       |            // The transaction must appear in the chunk.
 1412|  1.62M|            Assume(chunk[cluster_idx]);
  ------------------
  |  |  128|  1.62M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1413|       |            // Construct a new element in ret.
 1414|  1.62M|            auto& entry = ret.emplace_back();
 1415|  1.62M|            entry.m_chunk_feerate = FeePerWeight::FromFeeFrac(chunk_feerate);
 1416|  1.62M|            entry.m_index = m_mapping[cluster_idx];
 1417|       |            // If this is not the first transaction of the cluster linearization, it has an
 1418|       |            // implicit dependency on its predecessor.
 1419|  1.62M|            if (pos != 0) deps.emplace_back(prev_index, entry.m_index);
  ------------------
  |  Branch (1419:17): [True: 1.44M, False: 176k]
  ------------------
 1420|  1.62M|            prev_index = entry.m_index;
 1421|  1.62M|            entry.m_tx_size = m_depgraph.FeeRate(cluster_idx).size;
 1422|  1.62M|            size += entry.m_tx_size;
 1423|  1.62M|            ++pos;
 1424|  1.62M|        }
 1425|   962k|    }
 1426|   176k|    return size;
 1427|   176k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl15GetAncestorRefsERKNS_11TxGraphImplERNSt3__14spanINS4_4pairIPNS_7ClusterEjEELm18446744073709551615EEERNS4_6vectorIPN7TxGraph3RefENS4_9allocatorISF_EEEE:
 2317|   519k|{
 2318|       |    /** The union of all ancestors to be returned. */
 2319|   519k|    SetType ancestors_union;
 2320|       |    // Process elements from the front of args, as long as they apply.
 2321|  1.04M|    while (!args.empty()) {
  ------------------
  |  Branch (2321:12): [True: 523k, False: 518k]
  ------------------
 2322|   523k|        if (args.front().first != this) break;
  ------------------
  |  Branch (2322:13): [True: 818, False: 523k]
  ------------------
 2323|   523k|        ancestors_union |= m_depgraph.Ancestors(args.front().second);
 2324|   523k|        args = args.subspan(1);
 2325|   523k|    }
 2326|   519k|    Assume(ancestors_union.Any());
  ------------------
  |  |  128|   519k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2327|       |    // Translate all ancestors (in arbitrary order) to Refs (if they have any), and return them.
 2328|  2.47M|    for (auto idx : ancestors_union) {
  ------------------
  |  Branch (2328:19): [True: 2.47M, False: 519k]
  ------------------
 2329|  2.47M|        const auto& entry = graph.m_entries[m_mapping[idx]];
 2330|  2.47M|        Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|  2.47M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2331|  2.47M|        output.push_back(entry.m_ref);
 2332|  2.47M|    }
 2333|   519k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl17GetDescendantRefsERKNS_11TxGraphImplERNSt3__14spanINS4_4pairIPNS_7ClusterEjEELm18446744073709551615EEERNS4_6vectorIPN7TxGraph3RefENS4_9allocatorISF_EEEE:
 2348|   518k|{
 2349|       |    /** The union of all descendants to be returned. */
 2350|   518k|    SetType descendants_union;
 2351|       |    // Process elements from the front of args, as long as they apply.
 2352|  1.04M|    while (!args.empty()) {
  ------------------
  |  Branch (2352:12): [True: 523k, False: 518k]
  ------------------
 2353|   523k|        if (args.front().first != this) break;
  ------------------
  |  Branch (2353:13): [True: 537, False: 522k]
  ------------------
 2354|   522k|        descendants_union |= m_depgraph.Descendants(args.front().second);
 2355|   522k|        args = args.subspan(1);
 2356|   522k|    }
 2357|   518k|    Assume(descendants_union.Any());
  ------------------
  |  |  128|   518k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2358|       |    // Translate all descendants (in arbitrary order) to Refs (if they have any), and return them.
 2359|  2.47M|    for (auto idx : descendants_union) {
  ------------------
  |  Branch (2359:19): [True: 2.47M, False: 518k]
  ------------------
 2360|  2.47M|        const auto& entry = graph.m_entries[m_mapping[idx]];
 2361|  2.47M|        Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|  2.47M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2362|  2.47M|        output.push_back(entry.m_ref);
 2363|  2.47M|    }
 2364|   518k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl14GetClusterRefsERNS_11TxGraphImplENSt3__14spanIPN7TxGraph3RefELm18446744073709551615EEEj:
 2373|   973k|{
 2374|       |    // Translate the transactions in the Cluster (in linearization order, starting at start_pos in
 2375|       |    // the linearization) to Refs, and fill them in range.
 2376|  31.1M|    for (auto& ref : range) {
  ------------------
  |  Branch (2376:20): [True: 31.1M, False: 973k]
  ------------------
 2377|  31.1M|        Assume(start_pos < m_linearization.size());
  ------------------
  |  |  128|  31.1M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2378|  31.1M|        const auto& entry = graph.m_entries[m_mapping[m_linearization[start_pos++]]];
 2379|  31.1M|        Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|  31.1M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2380|  31.1M|        ref = entry.m_ref;
 2381|  31.1M|    }
 2382|       |    // Return whether start_pos has advanced to the end of the Cluster.
 2383|   973k|    return start_pos == m_linearization.size();
 2384|   973k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl20GetIndividualFeerateEj:
 2398|   630k|{
 2399|   630k|    return FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(idx));
 2400|   630k|}
txgraph.cpp:_ZN12_GLOBAL__N_118GenericClusterImpl6SetFeeERNS_11TxGraphImplEijl:
 2722|  2.05k|{
 2723|       |    // Make sure the specified DepGraphIndex exists in this Cluster.
 2724|  2.05k|    Assume(m_depgraph.Positions()[idx]);
  ------------------
  |  |  128|  2.05k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2725|       |    // Bail out if the fee isn't actually being changed.
 2726|  2.05k|    if (m_depgraph.FeeRate(idx).fee == fee) return;
  ------------------
  |  Branch (2726:9): [True: 520, False: 1.53k]
  ------------------
 2727|       |    // Update the fee, remember that relinearization will be necessary, and update the Entries
 2728|       |    // in the same Cluster.
 2729|  1.53k|    m_depgraph.FeeRate(idx).fee = fee;
 2730|  1.53k|    if (m_quality == QualityLevel::OVERSIZED_SINGLETON) {
  ------------------
  |  Branch (2730:9): [True: 0, False: 1.53k]
  ------------------
 2731|       |        // Nothing to do.
 2732|  1.53k|    } else if (IsAcceptable()) {
  ------------------
  |  Branch (2732:16): [True: 743, False: 794]
  ------------------
 2733|    743|        graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
 2734|    743|    }
 2735|  1.53k|    Updated(graph, /*level=*/level, /*rename=*/false);
 2736|  1.53k|}
txgraph.cpp:_ZNK12_GLOBAL__N_118GenericClusterImpl11SanityCheckERKNS_11TxGraphImplEi:
 2837|  51.5k|{
 2838|       |    // There must be an m_mapping for each m_depgraph position (including holes).
 2839|  51.5k|    assert(m_depgraph.PositionRange() == m_mapping.size());
  ------------------
  |  Branch (2839:5): [True: 51.5k, False: 0]
  ------------------
 2840|       |    // The linearization for this Cluster must contain every transaction once.
 2841|  51.5k|    assert(m_depgraph.TxCount() == m_linearization.size());
  ------------------
  |  Branch (2841:5): [True: 51.5k, False: 0]
  ------------------
 2842|       |    // Unless a split is to be applied, the cluster cannot have any holes.
 2843|  51.5k|    if (!NeedsSplitting()) {
  ------------------
  |  Branch (2843:9): [True: 50.7k, False: 772]
  ------------------
 2844|  50.7k|        assert(m_depgraph.Positions() == SetType::Fill(m_depgraph.TxCount()));
  ------------------
  |  Branch (2844:9): [True: 50.7k, False: 0]
  ------------------
 2845|  50.7k|    }
 2846|       |
 2847|       |    // Compute the chunking of m_linearization.
 2848|  51.5k|    auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
 2849|  51.5k|    unsigned chunk_num{0};
 2850|       |
 2851|       |    // Verify m_linearization.
 2852|  51.5k|    SetType m_done;
 2853|  51.5k|    LinearizationIndex linindex{0};
 2854|  51.5k|    DepGraphIndex chunk_pos{0}; //!< position within the current chunk
 2855|  51.5k|    assert(m_depgraph.IsAcyclic());
  ------------------
  |  Branch (2855:5): [True: 51.5k, False: 0]
  ------------------
 2856|  51.5k|    if (m_linearization.empty()) return;
  ------------------
  |  Branch (2856:9): [True: 265, False: 51.2k]
  ------------------
 2857|  51.2k|    FeeFrac equal_feerate_prefix = linchunking[chunk_num].feerate;
 2858|   602k|    for (auto lin_pos : m_linearization) {
  ------------------
  |  Branch (2858:23): [True: 602k, False: 51.2k]
  ------------------
 2859|   602k|        assert(lin_pos < m_mapping.size());
  ------------------
  |  Branch (2859:9): [True: 602k, False: 0]
  ------------------
 2860|   602k|        const auto& entry = graph.m_entries[m_mapping[lin_pos]];
 2861|       |        // Check that the linearization is topological.
 2862|   602k|        m_done.Set(lin_pos);
 2863|   602k|        if (IsTopological()) {
  ------------------
  |  Branch (2863:13): [True: 563k, False: 39.6k]
  ------------------
 2864|   563k|            assert(m_done.IsSupersetOf(m_depgraph.Ancestors(lin_pos)));
  ------------------
  |  Branch (2864:13): [True: 563k, False: 0]
  ------------------
 2865|   563k|        }
 2866|       |        // Check that the Entry has a locator pointing back to this Cluster & position within it.
 2867|   602k|        assert(entry.m_locator[level].cluster == this);
  ------------------
  |  Branch (2867:9): [True: 602k, False: 0]
  ------------------
 2868|   602k|        assert(entry.m_locator[level].index == lin_pos);
  ------------------
  |  Branch (2868:9): [True: 602k, False: 0]
  ------------------
 2869|       |        // For main-level entries, check linearization position and chunk feerate.
 2870|   602k|        if (level == 0 && IsAcceptable()) {
  ------------------
  |  Branch (2870:13): [True: 414k, False: 188k]
  |  Branch (2870:27): [True: 372k, False: 42.4k]
  ------------------
 2871|   372k|            assert(entry.m_main_lin_index == linindex);
  ------------------
  |  Branch (2871:13): [True: 372k, False: 0]
  ------------------
 2872|   372k|            ++linindex;
 2873|   372k|            if (!linchunking[chunk_num].transactions[lin_pos]) {
  ------------------
  |  Branch (2873:17): [True: 202k, False: 169k]
  ------------------
 2874|       |                // First transaction of a new chunk.
 2875|   202k|                ++chunk_num;
 2876|   202k|                assert(chunk_num < linchunking.size());
  ------------------
  |  Branch (2876:17): [True: 202k, False: 0]
  ------------------
 2877|   202k|                chunk_pos = 0;
 2878|   202k|                if (ByRatio{linchunking[chunk_num].feerate} < ByRatio{equal_feerate_prefix}) {
  ------------------
  |  Branch (2878:21): [True: 48.7k, False: 153k]
  ------------------
 2879|  48.7k|                    equal_feerate_prefix = linchunking[chunk_num].feerate;
 2880|   153k|                } else {
 2881|   153k|                    assert(ByRatio{linchunking[chunk_num].feerate} == ByRatio{equal_feerate_prefix});
  ------------------
  |  Branch (2881:21): [True: 153k, False: 0]
  ------------------
 2882|   153k|                    equal_feerate_prefix += linchunking[chunk_num].feerate;
 2883|   153k|                }
 2884|   202k|            }
 2885|   372k|            assert(entry.m_main_chunk_feerate == linchunking[chunk_num].feerate);
  ------------------
  |  Branch (2885:13): [True: 372k, False: 0]
  ------------------
 2886|   372k|            assert(entry.m_main_equal_feerate_chunk_prefix_size == equal_feerate_prefix.size);
  ------------------
  |  Branch (2886:13): [True: 372k, False: 0]
  ------------------
 2887|       |            // Verify that an entry in the chunk index exists for every chunk-ending transaction.
 2888|   372k|            ++chunk_pos;
 2889|   372k|            if (graph.m_main_clusterset.m_to_remove.empty()) {
  ------------------
  |  Branch (2889:17): [True: 349k, False: 22.2k]
  ------------------
 2890|   349k|                bool is_chunk_end = (chunk_pos == linchunking[chunk_num].transactions.Count());
 2891|   349k|                assert((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end()) == is_chunk_end);
  ------------------
  |  Branch (2891:17): [True: 349k, False: 0]
  ------------------
 2892|   349k|                if (is_chunk_end) {
  ------------------
  |  Branch (2892:21): [True: 222k, False: 127k]
  ------------------
 2893|   222k|                    auto& chunk_data = *entry.m_main_chunkindex_iterator;
 2894|   222k|                    if (m_done == m_depgraph.Positions() && chunk_pos == 1) {
  ------------------
  |  Branch (2894:25): [True: 29.8k, False: 192k]
  |  Branch (2894:61): [True: 24.1k, False: 5.77k]
  ------------------
 2895|  24.1k|                        assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
  ------------------
  |  Branch (2895:25): [True: 24.1k, False: 0]
  ------------------
 2896|   198k|                    } else {
 2897|   198k|                        assert(chunk_data.m_chunk_count == chunk_pos);
  ------------------
  |  Branch (2897:25): [True: 198k, False: 0]
  ------------------
 2898|   198k|                    }
 2899|   222k|                }
 2900|   349k|            }
 2901|       |            // If this Cluster has an acceptable quality level, its chunks must be connected.
 2902|   372k|            assert(m_depgraph.IsConnected(linchunking[chunk_num].transactions));
  ------------------
  |  Branch (2902:13): [True: 372k, False: 0]
  ------------------
 2903|   372k|        }
 2904|   602k|    }
 2905|       |    // Verify that each element of m_depgraph occurred in m_linearization.
 2906|  51.2k|    assert(m_done == m_depgraph.Positions());
  ------------------
  |  Branch (2906:5): [True: 51.2k, False: 0]
  ------------------
 2907|  51.2k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13GetClusterSetEi:
  836|   209M|{
  837|   209M|    if (level == 0) return m_main_clusterset;
  ------------------
  |  Branch (837:9): [True: 193M, False: 16.2M]
  ------------------
  838|  16.2M|    Assume(level == 1);
  ------------------
  |  |  128|  16.2M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  839|  16.2M|    Assume(m_staging_clusterset.has_value());
  ------------------
  |  |  128|  16.2M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  840|  16.2M|    return *m_staging_clusterset;
  841|   209M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13InsertClusterEiONSt3__110unique_ptrINS_7ClusterENS1_14default_deleteIS3_EEEENS_12QualityLevelE:
 1639|  3.56M|{
 1640|       |    // Cannot insert with quality level NONE (as that would mean not inserted).
 1641|  3.56M|    Assume(quality != QualityLevel::NONE);
  ------------------
  |  |  128|  3.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1642|       |    // The passed-in Cluster must not currently be in the TxGraphImpl.
 1643|  3.56M|    Assume(cluster->m_quality == QualityLevel::NONE);
  ------------------
  |  |  128|  3.56M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1644|       |
 1645|       |    // Append it at the end of the relevant TxGraphImpl::m_cluster.
 1646|  3.56M|    auto& clusterset = GetClusterSet(level);
 1647|  3.56M|    auto& quality_clusters = clusterset.m_clusters[int(quality)];
 1648|  3.56M|    ClusterSetIndex ret = quality_clusters.size();
 1649|  3.56M|    cluster->m_quality = quality;
 1650|  3.56M|    cluster->m_setindex = ret;
 1651|  3.56M|    quality_clusters.push_back(std::move(cluster));
 1652|  3.56M|    return ret;
 1653|  3.56M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17RemoveTransactionERKN7TxGraph3RefE:
 2263|  47.3k|{
 2264|       |    // Don't do anything if the Ref is empty (which may be indicative of the transaction already
 2265|       |    // having been removed).
 2266|  47.3k|    if (GetRefGraph(arg) == nullptr) return;
  ------------------
  |  Branch (2266:9): [True: 2.44k, False: 44.9k]
  ------------------
 2267|  44.9k|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|  44.9k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2268|  44.9k|    Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
  ------------------
  |  |  128|  45.8k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 44.0k, False: 897]
  |  |  |  Branch (128:51): [True: 897, False: 0]
  |  |  ------------------
  ------------------
 2269|       |    // Find the Cluster the transaction is in, and stop if it isn't in any.
 2270|  44.9k|    int level = GetTopLevel();
 2271|  44.9k|    auto cluster = FindCluster(GetRefIndex(arg), level);
 2272|  44.9k|    if (cluster == nullptr) return;
  ------------------
  |  Branch (2272:9): [True: 939, False: 43.9k]
  ------------------
 2273|       |    // Remember that the transaction is to be removed.
 2274|  43.9k|    auto& clusterset = GetClusterSet(level);
 2275|  43.9k|    clusterset.m_to_remove.push_back(GetRefIndex(arg));
 2276|       |    // Wipe m_group_data (as it will need to be recomputed).
 2277|  43.9k|    clusterset.m_group_data.reset();
 2278|  43.9k|    if (clusterset.m_oversized == true) clusterset.m_oversized = std::nullopt;
  ------------------
  |  Branch (2278:9): [True: 3.26k, False: 40.7k]
  ------------------
 2279|  43.9k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl11FindClusterEji:
  667|  48.5M|    Cluster* FindCluster(GraphIndex idx, int level) const noexcept { return FindClusterAndLevel(idx, level).first; }
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl19FindClusterAndLevelEji:
 1676|  68.2M|{
 1677|  68.2M|    Assume(level >= 0 && level <= GetTopLevel());
  ------------------
  |  |  128|   136M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 68.2M, False: 0]
  |  |  |  Branch (128:51): [True: 68.2M, False: 0]
  |  |  ------------------
  ------------------
 1678|  68.2M|    auto& entry = m_entries[idx];
 1679|       |    // Search the entry's locators from top to bottom.
 1680|   104M|    for (int l = level; l >= 0; --l) {
  ------------------
  |  Branch (1680:25): [True: 103M, False: 220k]
  ------------------
 1681|       |        // If the locator is missing, dig deeper; it may exist at a lower level and therefore be
 1682|       |        // implicitly existing at this level too.
 1683|   103M|        if (entry.m_locator[l].IsMissing()) continue;
  ------------------
  |  Branch (1683:13): [True: 35.9M, False: 67.9M]
  ------------------
 1684|       |        // If the locator has the entry marked as explicitly removed, stop.
 1685|  67.9M|        if (entry.m_locator[l].IsRemoved()) break;
  ------------------
  |  Branch (1685:13): [True: 1.23M, False: 66.7M]
  ------------------
 1686|       |        // Otherwise, we have found the topmost ClusterSet that contains this entry.
 1687|  66.7M|        return {entry.m_locator[l].cluster, l};
 1688|  67.9M|    }
 1689|       |    // If no non-empty locator was found, or an explicitly removed was hit, return nothing.
 1690|  1.45M|    return {nullptr, -1};
 1691|  68.2M|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl7Locator9IsMissingEv:
  594|   106M|        bool IsMissing() const noexcept { return cluster == nullptr && index == 0; }
  ------------------
  |  Branch (594:50): [True: 38.7M, False: 68.1M]
  |  Branch (594:72): [True: 37.4M, False: 1.30M]
  ------------------
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13AddDependencyERKN7TxGraph3RefES4_:
 2282|  3.73M|{
 2283|       |    // Don't do anything if either Ref is empty (which may be indicative of it having already been
 2284|       |    // removed).
 2285|  3.73M|    if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
  ------------------
  |  Branch (2285:9): [True: 1.59k, False: 3.73M]
  |  Branch (2285:43): [True: 1.62k, False: 3.73M]
  ------------------
 2286|  3.73M|    Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
  ------------------
  |  |  128|  7.47M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 3.73M, False: 0]
  |  |  |  Branch (128:51): [True: 3.73M, False: 0]
  |  |  ------------------
  ------------------
 2287|  3.73M|    Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
  ------------------
  |  |  128|  3.75M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 3.71M, False: 21.3k]
  |  |  |  Branch (128:51): [True: 21.3k, False: 0]
  |  |  ------------------
  ------------------
 2288|       |    // Don't do anything if this is a dependency on self.
 2289|  3.73M|    if (GetRefIndex(parent) == GetRefIndex(child)) return;
  ------------------
  |  Branch (2289:9): [True: 747, False: 3.73M]
  ------------------
 2290|       |    // Find the Cluster the parent and child transaction are in, and stop if either appears to be
 2291|       |    // already removed.
 2292|  3.73M|    int level = GetTopLevel();
 2293|  3.73M|    auto par_cluster = FindCluster(GetRefIndex(parent), level);
 2294|  3.73M|    if (par_cluster == nullptr) return;
  ------------------
  |  Branch (2294:9): [True: 940, False: 3.73M]
  ------------------
 2295|  3.73M|    auto chl_cluster = FindCluster(GetRefIndex(child), level);
 2296|  3.73M|    if (chl_cluster == nullptr) return;
  ------------------
  |  Branch (2296:9): [True: 843, False: 3.73M]
  ------------------
 2297|       |    // Remember that this dependency is to be applied.
 2298|  3.73M|    auto& clusterset = GetClusterSet(level);
 2299|  3.73M|    clusterset.m_deps_to_add.emplace_back(GetRefIndex(parent), GetRefIndex(child));
 2300|       |    // Wipe m_group_data (as it will need to be recomputed).
 2301|  3.73M|    clusterset.m_group_data.reset();
 2302|  3.73M|    if (clusterset.m_oversized == false) clusterset.m_oversized = std::nullopt;
  ------------------
  |  Branch (2302:9): [True: 115k, False: 3.61M]
  ------------------
 2303|  3.73M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17SetTransactionFeeERKN7TxGraph3RefEl:
 2747|  10.2k|{
 2748|       |    // Don't do anything if the passed Ref is empty.
 2749|  10.2k|    if (GetRefGraph(ref) == nullptr) return;
  ------------------
  |  Branch (2749:9): [True: 1.44k, False: 8.83k]
  ------------------
 2750|  8.83k|    Assume(GetRefGraph(ref) == this);
  ------------------
  |  |  128|  8.83k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2751|  8.83k|    Assume(m_main_chunkindex_observers == 0);
  ------------------
  |  |  128|  8.83k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2752|       |    // Find the entry, its locator, and inform its Cluster about the new feerate, if any.
 2753|  8.83k|    auto& entry = m_entries[GetRefIndex(ref)];
 2754|  26.4k|    for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (2754:25): [True: 17.6k, False: 8.83k]
  ------------------
 2755|  17.6k|        auto& locator = entry.m_locator[level];
 2756|  17.6k|        if (locator.IsPresent()) {
  ------------------
  |  Branch (2756:13): [True: 9.00k, False: 8.65k]
  ------------------
 2757|  9.00k|            locator.cluster->SetFee(*this, level, locator.index, fee);
 2758|  9.00k|        }
 2759|  17.6k|    }
 2760|  8.83k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl6DoWorkEm:
 3127|  24.8k|{
 3128|  24.8k|    uint64_t cost_done{0};
 3129|       |    // First linearize everything in NEEDS_RELINEARIZE to an acceptable level. If more budget
 3130|       |    // remains after that, try to make everything optimal.
 3131|  70.3k|    for (QualityLevel quality : {QualityLevel::NEEDS_FIX, QualityLevel::NEEDS_RELINEARIZE, QualityLevel::ACCEPTABLE}) {
  ------------------
  |  Branch (3131:31): [True: 70.3k, False: 17.6k]
  ------------------
 3132|       |        // First linearize staging, if it exists, then main.
 3133|   146k|        for (int level = GetTopLevel(); level >= 0; --level) {
  ------------------
  |  Branch (3133:41): [True: 83.0k, False: 63.1k]
  ------------------
 3134|       |            // Do not modify main if it has any observers.
 3135|  83.0k|            if (level == 0 && m_main_chunkindex_observers != 0) continue;
  ------------------
  |  Branch (3135:17): [True: 68.1k, False: 14.9k]
  |  Branch (3135:31): [True: 3.59k, False: 64.5k]
  ------------------
 3136|  79.4k|            ApplyDependencies(level);
 3137|  79.4k|            auto& clusterset = GetClusterSet(level);
 3138|       |            // Do not modify oversized levels.
 3139|  79.4k|            if (clusterset.m_oversized == true) continue;
  ------------------
  |  Branch (3139:17): [True: 14.1k, False: 65.3k]
  ------------------
 3140|  65.3k|            auto& queue = clusterset.m_clusters[int(quality)];
 3141|  77.2k|            while (!queue.empty()) {
  ------------------
  |  Branch (3141:20): [True: 19.0k, False: 58.2k]
  ------------------
 3142|  19.0k|                if (cost_done >= max_cost) return false;
  ------------------
  |  Branch (3142:21): [True: 1.84k, False: 17.1k]
  ------------------
 3143|       |                // Randomize the order in which we process, so that if the first cluster somehow
 3144|       |                // needs more work than what max_cost allows, we don't keep spending it on the same
 3145|       |                // one.
 3146|  17.1k|                auto pos = m_rng.randrange<size_t>(queue.size());
 3147|  17.1k|                auto cost_now = max_cost - cost_done;
 3148|  17.1k|                if (quality == QualityLevel::NEEDS_FIX || quality == QualityLevel::NEEDS_RELINEARIZE) {
  ------------------
  |  Branch (3148:21): [True: 8.48k, False: 8.70k]
  |  Branch (3148:59): [True: 1.99k, False: 6.70k]
  ------------------
 3149|       |                    // If we're working with clusters that need relinearization still, only perform
 3150|       |                    // up to m_acceptable_cost work. If they become ACCEPTABLE, and we still
 3151|       |                    // have budget after all other clusters are ACCEPTABLE too, we'll spend the
 3152|       |                    // remaining budget on trying to make them OPTIMAL.
 3153|  10.4k|                    cost_now = std::min(cost_now, m_acceptable_cost);
 3154|  10.4k|                }
 3155|  17.1k|                auto [cost, improved] = queue[pos].get()->Relinearize(*this, level, cost_now);
 3156|  17.1k|                cost_done += cost;
 3157|       |                // If no improvement was made to the Cluster, it means we've essentially run out of
 3158|       |                // budget. Even though it may be the case that cost_done < max_cost still, the
 3159|       |                // linearizer decided there wasn't enough budget left to attempt anything with.
 3160|       |                // To avoid an infinite loop that keeps trying clusters with minuscule budgets,
 3161|       |                // stop here too.
 3162|  17.1k|                if (!improved) return false;
  ------------------
  |  Branch (3162:21): [True: 5.32k, False: 11.8k]
  ------------------
 3163|  17.1k|            }
 3164|  65.3k|        }
 3165|  70.3k|    }
 3166|       |    // All possible work has been performed, so we can return true. Note that this does *not* mean
 3167|       |    // that all clusters are optimally linearized now. It may be that there is nothing to do left
 3168|       |    // because all non-optimal clusters are in oversized and/or observer-bearing levels.
 3169|  17.6k|    return true;
 3170|  24.8k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17ApplyDependenciesEi:
 2115|  92.5M|{
 2116|  92.5M|    auto& clusterset = GetClusterSet(level);
 2117|       |    // Do not bother computing groups if we already know the result will be oversized.
 2118|  92.5M|    if (clusterset.m_oversized == true) return;
  ------------------
  |  Branch (2118:9): [True: 49.1k, False: 92.4M]
  ------------------
 2119|       |    // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
 2120|  92.4M|    GroupClusters(level);
 2121|  92.4M|    Assume(clusterset.m_group_data.has_value());
  ------------------
  |  |  128|  92.4M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2122|       |    // Nothing to do if there are no dependencies to be added.
 2123|  92.4M|    if (clusterset.m_deps_to_add.empty()) return;
  ------------------
  |  Branch (2123:9): [True: 92.4M, False: 76.8k]
  ------------------
 2124|       |    // Dependencies cannot be applied if it would result in oversized clusters.
 2125|  76.8k|    if (clusterset.m_oversized == true) return;
  ------------------
  |  Branch (2125:9): [True: 2.87k, False: 74.0k]
  ------------------
 2126|       |
 2127|       |    // For each group of to-be-merged Clusters.
 2128|   216k|    for (const auto& group_entry : clusterset.m_group_data->m_groups) {
  ------------------
  |  Branch (2128:34): [True: 216k, False: 74.0k]
  ------------------
 2129|   216k|        auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
 2130|   216k|                                .subspan(group_entry.m_cluster_offset, group_entry.m_cluster_count);
 2131|       |        // Pull in all the Clusters that contain dependencies.
 2132|   216k|        if (level == 1) {
  ------------------
  |  Branch (2132:13): [True: 174k, False: 41.9k]
  ------------------
 2133|  1.27M|            for (Cluster*& cluster : cluster_span) {
  ------------------
  |  Branch (2133:36): [True: 1.27M, False: 174k]
  ------------------
 2134|  1.27M|                cluster = PullIn(cluster, cluster->GetLevel(*this));
 2135|  1.27M|            }
 2136|   174k|        }
 2137|       |        // Invoke Merge() to merge them into a single Cluster.
 2138|   216k|        Merge(cluster_span, level);
 2139|       |        // Actually apply all to-be-added dependencies (all parents and children from this grouping
 2140|       |        // belong to the same Cluster at this point because of the merging above).
 2141|   216k|        auto deps_span = std::span{clusterset.m_deps_to_add}
 2142|   216k|                             .subspan(group_entry.m_deps_offset, group_entry.m_deps_count);
 2143|   216k|        Assume(!deps_span.empty());
  ------------------
  |  |  128|   216k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2144|   216k|        const auto& loc = m_entries[deps_span[0].second].m_locator[level];
 2145|   216k|        Assume(loc.IsPresent());
  ------------------
  |  |  128|   216k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2146|   216k|        loc.cluster->ApplyDependencies(*this, level, deps_span);
 2147|   216k|    }
 2148|       |
 2149|       |    // Wipe the list of to-be-added dependencies now that they are applied.
 2150|  74.0k|    clusterset.m_deps_to_add.clear();
 2151|  74.0k|    Compact();
 2152|       |    // Also no further Cluster mergings are needed (note that we clear, but don't set to
 2153|       |    // std::nullopt, as that would imply the groupings are unknown).
 2154|  74.0k|    clusterset.m_group_data = GroupData{};
 2155|  74.0k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEi:
 1857|  92.6M|{
 1858|  92.6M|    auto& clusterset = GetClusterSet(level);
 1859|       |    // If the groupings have been computed already, nothing is left to be done.
 1860|  92.6M|    if (clusterset.m_group_data.has_value()) return;
  ------------------
  |  Branch (1860:9): [True: 92.4M, False: 219k]
  ------------------
 1861|       |
 1862|       |    // Before computing which Clusters need to be merged together, first apply all removals and
 1863|       |    // split the Clusters into connected components. If we would group first, we might end up
 1864|       |    // with inefficient and/or oversized Clusters which just end up being split again anyway.
 1865|   219k|    SplitAll(level);
 1866|       |
 1867|       |    /** Annotated clusters: an entry for each Cluster, together with the sequence number for the
 1868|       |     *  representative for the partition it is in (initially its own, later that of the
 1869|       |     *  to-be-merged group). */
 1870|   219k|    std::vector<std::pair<Cluster*, uint64_t>> an_clusters;
 1871|       |    /** Annotated dependencies: an entry for each m_deps_to_add entry (excluding ones that apply
 1872|       |     *  to removed transactions), together with the sequence number of the representative root of
 1873|       |     *  Clusters it applies to (initially that of the child Cluster, later that of the
 1874|       |     *  to-be-merged group). */
 1875|   219k|    std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t>> an_deps;
 1876|       |
 1877|       |    // Construct an an_clusters entry for every oversized cluster, including ones from levels below,
 1878|       |    // as they may be inherited in this one.
 1879|   587k|    for (int level_iter = 0; level_iter <= level; ++level_iter) {
  ------------------
  |  Branch (1879:30): [True: 367k, False: 219k]
  ------------------
 1880|   431k|        for (auto& cluster : GetClusterSet(level_iter).m_clusters[int(QualityLevel::OVERSIZED_SINGLETON)]) {
  ------------------
  |  Branch (1880:28): [True: 431k, False: 367k]
  ------------------
 1881|   431k|            auto graph_idx = cluster->GetClusterEntry(0);
 1882|   431k|            auto cur_cluster = FindCluster(graph_idx, level);
 1883|   431k|            if (cur_cluster == nullptr) continue;
  ------------------
  |  Branch (1883:17): [True: 177k, False: 254k]
  ------------------
 1884|   254k|            an_clusters.emplace_back(cur_cluster, cur_cluster->m_sequence);
 1885|   254k|        }
 1886|   367k|    }
 1887|       |
 1888|       |    // Construct a an_clusters entry for every parent and child in the to-be-applied dependencies,
 1889|       |    // and an an_deps entry for each dependency to be applied.
 1890|   219k|    an_deps.reserve(clusterset.m_deps_to_add.size());
 1891|  7.82M|    for (const auto& [par, chl] : clusterset.m_deps_to_add) {
  ------------------
  |  Branch (1891:33): [True: 7.82M, False: 219k]
  ------------------
 1892|  7.82M|        auto par_cluster = FindCluster(par, level);
 1893|  7.82M|        auto chl_cluster = FindCluster(chl, level);
 1894|       |        // Skip dependencies for which the parent or child transaction is removed.
 1895|  7.82M|        if (par_cluster == nullptr || chl_cluster == nullptr) continue;
  ------------------
  |  Branch (1895:13): [True: 294k, False: 7.52M]
  |  Branch (1895:39): [True: 647k, False: 6.88M]
  ------------------
 1896|  6.88M|        an_clusters.emplace_back(par_cluster, par_cluster->m_sequence);
 1897|       |        // Do not include a duplicate when parent and child are identical, as it'll be removed
 1898|       |        // below anyway.
 1899|  6.88M|        if (chl_cluster != par_cluster) an_clusters.emplace_back(chl_cluster, chl_cluster->m_sequence);
  ------------------
  |  Branch (1899:13): [True: 6.83M, False: 44.8k]
  ------------------
 1900|       |        // Add entry to an_deps, using the child sequence number.
 1901|  6.88M|        an_deps.emplace_back(std::pair{par, chl}, chl_cluster->m_sequence);
 1902|  6.88M|    }
 1903|       |    // Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
 1904|       |    // to which dependencies apply, or which are oversized.
 1905|   219k|    std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
 1906|   219k|    an_clusters.erase(std::ranges::unique(an_clusters).begin(), an_clusters.end());
 1907|       |    // Sort an_deps by applying the same order to the involved child cluster.
 1908|   219k|    std::ranges::sort(an_deps, [&](auto& a, auto& b) noexcept { return a.second < b.second; });
 1909|       |
 1910|       |    // Run the union-find algorithm to find partitions of the input Clusters which need to be
 1911|       |    // grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
 1912|   219k|    {
 1913|       |        /** Each PartitionData entry contains information about a single input Cluster. */
 1914|   219k|        struct PartitionData
 1915|   219k|        {
 1916|       |            /** The sequence number of the cluster this holds information for. */
 1917|   219k|            uint64_t sequence;
 1918|       |            /** All PartitionData entries belonging to the same partition are organized in a tree.
 1919|       |             *  Each element points to its parent, or to itself if it is the root. The root is then
 1920|       |             *  a representative for the entire tree, and can be found by walking upwards from any
 1921|       |             *  element. */
 1922|   219k|            PartitionData* parent;
 1923|       |            /** (only if this is a root, so when parent == this) An upper bound on the height of
 1924|       |             *  tree for this partition. */
 1925|   219k|            unsigned rank;
 1926|   219k|        };
 1927|       |        /** Information about each input Cluster. Sorted by Cluster::m_sequence. */
 1928|   219k|        std::vector<PartitionData> partition_data;
 1929|       |
 1930|       |        /** Given a Cluster, find its corresponding PartitionData. */
 1931|   219k|        auto locate_fn = [&](uint64_t sequence) noexcept -> PartitionData* {
 1932|   219k|            auto it = std::lower_bound(partition_data.begin(), partition_data.end(), sequence,
 1933|   219k|                                       [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
 1934|   219k|            Assume(it != partition_data.end());
 1935|   219k|            Assume(it->sequence == sequence);
 1936|   219k|            return &*it;
 1937|   219k|        };
 1938|       |
 1939|       |        /** Given a PartitionData, find the root of the tree it is in (its representative). */
 1940|   219k|        static constexpr auto find_root_fn = [](PartitionData* data) noexcept -> PartitionData* {
 1941|   219k|            while (data->parent != data) {
 1942|       |                // Replace pointers to parents with pointers to grandparents.
 1943|       |                // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
 1944|   219k|                auto par = data->parent;
 1945|   219k|                data->parent = par->parent;
 1946|   219k|                data = par;
 1947|   219k|            }
 1948|   219k|            return data;
 1949|   219k|        };
 1950|       |
 1951|       |        /** Given two PartitionDatas, union the partitions they are in, and return their
 1952|       |         *  representative. */
 1953|   219k|        static constexpr auto union_fn = [](PartitionData* arg1, PartitionData* arg2) noexcept {
 1954|       |            // Find the roots of the trees, and bail out if they are already equal (which would
 1955|       |            // mean they are in the same partition already).
 1956|   219k|            auto rep1 = find_root_fn(arg1);
 1957|   219k|            auto rep2 = find_root_fn(arg2);
 1958|   219k|            if (rep1 == rep2) return rep1;
 1959|       |            // Pick the lower-rank root to become a child of the higher-rank one.
 1960|       |            // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_rank.
 1961|   219k|            if (rep1->rank < rep2->rank) std::swap(rep1, rep2);
 1962|   219k|            rep2->parent = rep1;
 1963|   219k|            rep1->rank += (rep1->rank == rep2->rank);
 1964|   219k|            return rep1;
 1965|   219k|        };
 1966|       |
 1967|       |        // Start by initializing every Cluster as its own singleton partition.
 1968|   219k|        partition_data.resize(an_clusters.size());
 1969|  4.34M|        for (size_t i = 0; i < an_clusters.size(); ++i) {
  ------------------
  |  Branch (1969:28): [True: 4.12M, False: 219k]
  ------------------
 1970|  4.12M|            partition_data[i].sequence = an_clusters[i].first->m_sequence;
 1971|  4.12M|            partition_data[i].parent = &partition_data[i];
 1972|  4.12M|            partition_data[i].rank = 0;
 1973|  4.12M|        }
 1974|       |
 1975|       |        // Run through all parent/child pairs in an_deps, and union the partitions their Clusters
 1976|       |        // are in.
 1977|   219k|        Cluster* last_chl_cluster{nullptr};
 1978|   219k|        PartitionData* last_partition{nullptr};
 1979|  6.88M|        for (const auto& [dep, _] : an_deps) {
  ------------------
  |  Branch (1979:35): [True: 6.88M, False: 219k]
  ------------------
 1980|  6.88M|            auto [par, chl] = dep;
 1981|  6.88M|            auto par_cluster = FindCluster(par, level);
 1982|  6.88M|            auto chl_cluster = FindCluster(chl, level);
 1983|  6.88M|            Assume(chl_cluster != nullptr && par_cluster != nullptr);
  ------------------
  |  |  128|  13.7M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 6.88M, False: 0]
  |  |  |  Branch (128:51): [True: 6.88M, False: 0]
  |  |  ------------------
  ------------------
 1984|       |            // Nothing to do if parent and child are in the same Cluster.
 1985|  6.88M|            if (par_cluster == chl_cluster) continue;
  ------------------
  |  Branch (1985:17): [True: 44.8k, False: 6.83M]
  ------------------
 1986|  6.83M|            Assume(par != chl);
  ------------------
  |  |  128|  6.83M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1987|  6.83M|            if (chl_cluster == last_chl_cluster) {
  ------------------
  |  Branch (1987:17): [True: 4.04M, False: 2.79M]
  ------------------
 1988|       |                // If the child Clusters is the same as the previous iteration, union with the
 1989|       |                // tree they were in, avoiding the need for another lookup. Note that an_deps
 1990|       |                // is sorted by child Cluster, so batches with the same child are expected.
 1991|  4.04M|                last_partition = union_fn(locate_fn(par_cluster->m_sequence), last_partition);
 1992|  4.04M|            } else {
 1993|  2.79M|                last_chl_cluster = chl_cluster;
 1994|  2.79M|                last_partition = union_fn(locate_fn(par_cluster->m_sequence), locate_fn(chl_cluster->m_sequence));
 1995|  2.79M|            }
 1996|  6.83M|        }
 1997|       |
 1998|       |        // Update the sequence numbers in an_clusters and an_deps to be those of the partition
 1999|       |        // representative.
 2000|   219k|        auto deps_it = an_deps.begin();
 2001|  4.34M|        for (size_t i = 0; i < partition_data.size(); ++i) {
  ------------------
  |  Branch (2001:28): [True: 4.12M, False: 219k]
  ------------------
 2002|  4.12M|            auto& data = partition_data[i];
 2003|       |            // Find the sequence of the representative of the partition Cluster i is in, and store
 2004|       |            // it with the Cluster.
 2005|  4.12M|            auto rep_seq = find_root_fn(&data)->sequence;
 2006|  4.12M|            an_clusters[i].second = rep_seq;
 2007|       |            // Find all dependencies whose child Cluster is Cluster i, and annotate them with rep.
 2008|  11.0M|            while (deps_it != an_deps.end()) {
  ------------------
  |  Branch (2008:20): [True: 10.5M, False: 418k]
  ------------------
 2009|  10.5M|                auto [par, chl] = deps_it->first;
 2010|  10.5M|                auto chl_cluster = FindCluster(chl, level);
 2011|  10.5M|                Assume(chl_cluster != nullptr);
  ------------------
  |  |  128|  10.5M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2012|  10.5M|                if (chl_cluster->m_sequence > data.sequence) break;
  ------------------
  |  Branch (2012:21): [True: 3.70M, False: 6.88M]
  ------------------
 2013|  6.88M|                deps_it->second = rep_seq;
 2014|  6.88M|                ++deps_it;
 2015|  6.88M|            }
 2016|  4.12M|        }
 2017|   219k|    }
 2018|       |
 2019|       |    // Sort both an_clusters and an_deps by sequence number of the representative of the
 2020|       |    // partition they are in, grouping all those applying to the same partition together.
 2021|   219k|    std::ranges::sort(an_deps, [](auto& a, auto& b) noexcept { return a.second < b.second; });
 2022|   219k|    std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
 2023|       |
 2024|       |    // Translate the resulting cluster groups to the m_group_data structure, and the dependencies
 2025|       |    // back to m_deps_to_add.
 2026|   219k|    clusterset.m_group_data = GroupData{};
 2027|   219k|    clusterset.m_group_data->m_group_clusters.reserve(an_clusters.size());
 2028|   219k|    clusterset.m_deps_to_add.clear();
 2029|   219k|    clusterset.m_deps_to_add.reserve(an_deps.size());
 2030|   219k|    clusterset.m_oversized = false;
 2031|   219k|    auto an_deps_it = an_deps.begin();
 2032|   219k|    auto an_clusters_it = an_clusters.begin();
 2033|   950k|    while (an_clusters_it != an_clusters.end()) {
  ------------------
  |  Branch (2033:12): [True: 730k, False: 219k]
  ------------------
 2034|       |        // Process all clusters/dependencies belonging to the partition with representative rep.
 2035|   730k|        auto rep = an_clusters_it->second;
 2036|       |        // Create and initialize a new GroupData entry for the partition.
 2037|   730k|        auto& new_entry = clusterset.m_group_data->m_groups.emplace_back();
 2038|   730k|        new_entry.m_cluster_offset = clusterset.m_group_data->m_group_clusters.size();
 2039|   730k|        new_entry.m_cluster_count = 0;
 2040|   730k|        new_entry.m_deps_offset = clusterset.m_deps_to_add.size();
 2041|   730k|        new_entry.m_deps_count = 0;
 2042|   730k|        uint32_t total_count{0};
 2043|   730k|        uint64_t total_size{0};
 2044|       |        // Add all its clusters to it (copying those from an_clusters to m_group_clusters).
 2045|  4.85M|        while (an_clusters_it != an_clusters.end() && an_clusters_it->second == rep) {
  ------------------
  |  Branch (2045:16): [True: 4.66M, False: 193k]
  |  Branch (2045:16): [True: 4.12M, False: 730k]
  |  Branch (2045:55): [True: 4.12M, False: 536k]
  ------------------
 2046|  4.12M|            clusterset.m_group_data->m_group_clusters.push_back(an_clusters_it->first);
 2047|  4.12M|            total_count += an_clusters_it->first->GetTxCount();
 2048|  4.12M|            total_size += an_clusters_it->first->GetTotalTxSize();
 2049|  4.12M|            ++an_clusters_it;
 2050|  4.12M|            ++new_entry.m_cluster_count;
 2051|  4.12M|        }
 2052|       |        // Add all its dependencies to it (copying those back from an_deps to m_deps_to_add).
 2053|  7.61M|        while (an_deps_it != an_deps.end() && an_deps_it->second == rep) {
  ------------------
  |  Branch (2053:16): [True: 7.28M, False: 326k]
  |  Branch (2053:16): [True: 6.88M, False: 730k]
  |  Branch (2053:47): [True: 6.88M, False: 403k]
  ------------------
 2054|  6.88M|            clusterset.m_deps_to_add.push_back(an_deps_it->first);
 2055|  6.88M|            ++an_deps_it;
 2056|  6.88M|            ++new_entry.m_deps_count;
 2057|  6.88M|        }
 2058|       |        // Detect oversizedness.
 2059|   730k|        if (total_count > m_max_cluster_count || total_size > m_max_cluster_size) {
  ------------------
  |  Branch (2059:13): [True: 153k, False: 576k]
  |  Branch (2059:50): [True: 239k, False: 337k]
  ------------------
 2060|   393k|            clusterset.m_oversized = true;
 2061|   393k|        }
 2062|   730k|    }
 2063|   219k|    Assume(an_deps_it == an_deps.end());
  ------------------
  |  |  128|   219k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2064|   219k|    Assume(an_clusters_it == an_clusters.end());
  ------------------
  |  |  128|   219k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2065|   219k|    Compact();
 2066|   219k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl8SplitAllEi:
 1842|   312k|{
 1843|   312k|    Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
  ------------------
  |  |  128|   624k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 312k, False: 0]
  |  |  |  Branch (128:51): [True: 312k, False: 0]
  |  |  ------------------
  ------------------
 1844|       |    // Before splitting all Cluster, first make sure all removals are applied.
 1845|   312k|    ApplyRemovals(up_to_level);
 1846|   772k|    for (int level = 0; level <= up_to_level; ++level) {
  ------------------
  |  Branch (1846:25): [True: 460k, False: 312k]
  ------------------
 1847|   920k|        for (auto quality : {QualityLevel::NEEDS_SPLIT_FIX, QualityLevel::NEEDS_SPLIT}) {
  ------------------
  |  Branch (1847:27): [True: 920k, False: 460k]
  ------------------
 1848|   920k|            auto& queue = GetClusterSet(level).m_clusters[int(quality)];
 1849|  1.38M|            while (!queue.empty()) {
  ------------------
  |  Branch (1849:20): [True: 465k, False: 920k]
  ------------------
 1850|   465k|                Split(*queue.back().get(), level);
 1851|   465k|            }
 1852|   920k|        }
 1853|   460k|    }
 1854|   312k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13ApplyRemovalsEi:
 1709|  2.32M|{
 1710|  2.32M|    Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
  ------------------
  |  |  128|  4.65M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 2.32M, False: 0]
  |  |  |  Branch (128:51): [True: 2.32M, False: 0]
  |  |  ------------------
  ------------------
 1711|  5.21M|    for (int level = 0; level <= up_to_level; ++level) {
  ------------------
  |  Branch (1711:25): [True: 2.88M, False: 2.32M]
  ------------------
 1712|  2.88M|        auto& clusterset = GetClusterSet(level);
 1713|  2.88M|        auto& to_remove = clusterset.m_to_remove;
 1714|       |        // Skip if there is nothing to remove in this level.
 1715|  2.88M|        if (to_remove.empty()) continue;
  ------------------
  |  Branch (1715:13): [True: 2.78M, False: 101k]
  ------------------
 1716|       |        // Pull in all Clusters that are not in staging.
 1717|   101k|        if (level == 1) {
  ------------------
  |  Branch (1717:13): [True: 65.4k, False: 35.6k]
  ------------------
 1718|   485k|            for (GraphIndex index : to_remove) {
  ------------------
  |  Branch (1718:35): [True: 485k, False: 65.4k]
  ------------------
 1719|   485k|                auto [cluster, cluster_level] = FindClusterAndLevel(index, level);
 1720|   485k|                if (cluster != nullptr) PullIn(cluster, cluster_level);
  ------------------
  |  Branch (1720:21): [True: 474k, False: 10.9k]
  ------------------
 1721|   485k|            }
 1722|  65.4k|        }
 1723|       |        // Group the set of to-be-removed entries by Cluster::m_sequence.
 1724|   101k|        std::ranges::sort(to_remove, [&](GraphIndex a, GraphIndex b) noexcept {
 1725|   101k|            Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
 1726|   101k|            Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
 1727|   101k|            return CompareClusters(cluster_a, cluster_b) < 0;
 1728|   101k|        });
 1729|       |        // Process per Cluster.
 1730|   101k|        std::span to_remove_span{to_remove};
 1731|   591k|        while (!to_remove_span.empty()) {
  ------------------
  |  Branch (1731:16): [True: 490k, False: 101k]
  ------------------
 1732|   490k|            Cluster* cluster = m_entries[to_remove_span.front()].m_locator[level].cluster;
 1733|   490k|            if (cluster != nullptr) {
  ------------------
  |  Branch (1733:17): [True: 479k, False: 11.3k]
  ------------------
 1734|       |                // If the first to_remove_span entry's Cluster exists, hand to_remove_span to it, so it
 1735|       |                // can pop off whatever applies to it.
 1736|   479k|                cluster->ApplyRemovals(*this, level, to_remove_span);
 1737|   479k|            } else {
 1738|       |                // Otherwise, skip this already-removed entry. This may happen when
 1739|       |                // RemoveTransaction was called twice on the same Ref, for example.
 1740|  11.3k|                to_remove_span = to_remove_span.subspan(1);
 1741|  11.3k|            }
 1742|   490k|        }
 1743|   101k|        to_remove.clear();
 1744|   101k|    }
 1745|  2.32M|    Compact();
 1746|  2.32M|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13ApplyRemovalsEiENK3$_0clEjj:
 1724|  20.3M|        std::ranges::sort(to_remove, [&](GraphIndex a, GraphIndex b) noexcept {
 1725|  20.3M|            Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
 1726|  20.3M|            Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
 1727|  20.3M|            return CompareClusters(cluster_a, cluster_b) < 0;
 1728|  20.3M|        });
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl5SplitERNS_7ClusterEi:
 1830|   465k|{
 1831|       |    // To split a Cluster, first make sure all removals are applied (as we might need to split
 1832|       |    // again afterwards otherwise).
 1833|   465k|    ApplyRemovals(level);
 1834|   465k|    bool del = cluster.Split(*this, level);
 1835|   465k|    if (del) {
  ------------------
  |  Branch (1835:9): [True: 460k, False: 5.75k]
  ------------------
 1836|       |        // Cluster::Split reports whether the Cluster is to be deleted.
 1837|   460k|        DeleteCluster(cluster, level);
 1838|   460k|    }
 1839|   465k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13DeleteClusterERNS_7ClusterEi:
 1668|  2.01M|{
 1669|       |    // Extract the cluster from where it currently resides.
 1670|  2.01M|    auto cluster_ptr = ExtractCluster(level, cluster.m_quality, cluster.m_setindex);
 1671|       |    // And throw it away.
 1672|  2.01M|    cluster_ptr.reset();
 1673|  2.01M|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_0clINSt3__14pairIPNS_7ClusterEmEES7_EEDaRT_RT0_:
 1905|   793M|    std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_1clINSt3__14pairINS4_IjjEEmEES6_EEDaRT_RT0_:
 1908|   493M|    std::ranges::sort(an_deps, [&](auto& a, auto& b) noexcept { return a.second < b.second; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_5clEPZNS0_13GroupClustersEiE13PartitionDataS3_:
 1953|  6.83M|        static constexpr auto union_fn = [](PartitionData* arg1, PartitionData* arg2) noexcept {
 1954|       |            // Find the roots of the trees, and bail out if they are already equal (which would
 1955|       |            // mean they are in the same partition already).
 1956|  6.83M|            auto rep1 = find_root_fn(arg1);
 1957|  6.83M|            auto rep2 = find_root_fn(arg2);
 1958|  6.83M|            if (rep1 == rep2) return rep1;
  ------------------
  |  Branch (1958:17): [True: 3.44M, False: 3.39M]
  ------------------
 1959|       |            // Pick the lower-rank root to become a child of the higher-rank one.
 1960|       |            // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_rank.
 1961|  3.39M|            if (rep1->rank < rep2->rank) std::swap(rep1, rep2);
  ------------------
  |  Branch (1961:17): [True: 1.17M, False: 2.22M]
  ------------------
 1962|  3.39M|            rep2->parent = rep1;
 1963|  3.39M|            rep1->rank += (rep1->rank == rep2->rank);
 1964|  3.39M|            return rep1;
 1965|  6.83M|        };
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_2clEm:
 1931|  9.62M|        auto locate_fn = [&](uint64_t sequence) noexcept -> PartitionData* {
 1932|  9.62M|            auto it = std::lower_bound(partition_data.begin(), partition_data.end(), sequence,
 1933|  9.62M|                                       [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
 1934|  9.62M|            Assume(it != partition_data.end());
  ------------------
  |  |  128|  9.62M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1935|  9.62M|            Assume(it->sequence == sequence);
  ------------------
  |  |  128|  9.62M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1936|  9.62M|            return &*it;
 1937|  9.62M|        };
txgraph.cpp:_ZZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_2clEmENKUlRT_mE_clIZNS0_13GroupClustersEiE13PartitionDataEEDaS3_m:
 1933|  53.2M|                                       [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_6clEPZNS0_13GroupClustersEiE13PartitionData:
 1940|  17.7M|        static constexpr auto find_root_fn = [](PartitionData* data) noexcept -> PartitionData* {
 1941|  27.0M|            while (data->parent != data) {
  ------------------
  |  Branch (1941:20): [True: 9.24M, False: 17.7M]
  ------------------
 1942|       |                // Replace pointers to parents with pointers to grandparents.
 1943|       |                // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
 1944|  9.24M|                auto par = data->parent;
 1945|  9.24M|                data->parent = par->parent;
 1946|  9.24M|                data = par;
 1947|  9.24M|            }
 1948|  17.7M|            return data;
 1949|  17.7M|        };
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_3clINSt3__14pairINS4_IjjEEmEES6_EEDaRT_RT0_:
 2021|   493M|    std::ranges::sort(an_deps, [](auto& a, auto& b) noexcept { return a.second < b.second; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl13GroupClustersEiENK3$_4clINSt3__14pairIPNS_7ClusterEmEES7_EEDaRT_RT0_:
 2022|   243M|    std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl6PullInEPNS_7ClusterEi:
 1694|  1.75M|{
 1695|  1.75M|    int to_level = GetTopLevel();
 1696|  1.75M|    Assume(to_level == 1);
  ------------------
  |  |  128|  1.75M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1697|  1.75M|    Assume(level <= to_level);
  ------------------
  |  |  128|  1.75M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1698|       |    // Copy the Cluster from main to staging, if it's not already there.
 1699|  1.75M|    if (level == 0) {
  ------------------
  |  Branch (1699:9): [True: 1.49M, False: 255k]
  ------------------
 1700|       |        // Make the Cluster Acceptable before copying. This isn't strictly necessary, but doing it
 1701|       |        // now avoids doing double work later.
 1702|  1.49M|        MakeAcceptable(*cluster, level);
 1703|  1.49M|        cluster = cluster->CopyToStaging(*this);
 1704|  1.49M|    }
 1705|  1.75M|    return cluster;
 1706|  1.75M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl14MakeAcceptableERNS_7ClusterEi:
 2208|   167M|{
 2209|       |    // Relinearize the Cluster if needed.
 2210|   167M|    if (!cluster.NeedsSplitting() && !cluster.IsAcceptable() && !cluster.IsOversized()) {
  ------------------
  |  Branch (2210:9): [True: 167M, False: 46]
  |  Branch (2210:38): [True: 410k, False: 166M]
  |  Branch (2210:65): [True: 207k, False: 203k]
  ------------------
 2211|   207k|        cluster.Relinearize(*this, level, m_acceptable_cost);
 2212|   207k|    }
 2213|   167M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl5MergeENSt3__14spanIPNS_7ClusterELm18446744073709551615EEEi:
 2069|   216k|{
 2070|   216k|    Assume(!to_merge.empty());
  ------------------
  |  |  128|   216k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2071|       |    // Nothing to do if a group consists of just a single Cluster.
 2072|   216k|    if (to_merge.size() == 1) return;
  ------------------
  |  Branch (2072:9): [True: 1.08k, False: 215k]
  ------------------
 2073|       |
 2074|       |    // Move the largest Cluster to the front of to_merge. As all transactions in other to-be-merged
 2075|       |    // Clusters will be moved to that one, putting the largest one first minimizes the number of
 2076|       |    // moves.
 2077|   215k|    size_t max_size_pos{0};
 2078|   215k|    DepGraphIndex max_size = to_merge[max_size_pos]->GetTxCount();
 2079|   215k|    GetClusterSet(level).m_cluster_usage -= to_merge[max_size_pos]->TotalMemoryUsage();
 2080|   215k|    DepGraphIndex total_size = max_size;
 2081|  1.58M|    for (size_t i = 1; i < to_merge.size(); ++i) {
  ------------------
  |  Branch (2081:24): [True: 1.37M, False: 215k]
  ------------------
 2082|  1.37M|        GetClusterSet(level).m_cluster_usage -= to_merge[i]->TotalMemoryUsage();
 2083|  1.37M|        DepGraphIndex size = to_merge[i]->GetTxCount();
 2084|  1.37M|        total_size += size;
 2085|  1.37M|        if (size > max_size) {
  ------------------
  |  Branch (2085:13): [True: 44.9k, False: 1.32M]
  ------------------
 2086|  44.9k|            max_size_pos = i;
 2087|  44.9k|            max_size = size;
 2088|  44.9k|        }
 2089|  1.37M|    }
 2090|   215k|    if (max_size_pos != 0) std::swap(to_merge[0], to_merge[max_size_pos]);
  ------------------
  |  Branch (2090:9): [True: 42.2k, False: 173k]
  ------------------
 2091|       |
 2092|   215k|    size_t start_idx = 1;
 2093|   215k|    Cluster* into_cluster = to_merge[0];
 2094|   215k|    if (total_size > into_cluster->GetMaxTxCount()) {
  ------------------
  |  Branch (2094:9): [True: 160k, False: 55.3k]
  ------------------
 2095|       |        // The into_merge cluster is too small to fit all transactions being merged. Construct a
 2096|       |        // a new Cluster using an implementation that matches the total size, and merge everything
 2097|       |        // in there.
 2098|   160k|        auto new_cluster = CreateEmptyCluster(total_size);
 2099|   160k|        into_cluster = new_cluster.get();
 2100|   160k|        InsertCluster(level, std::move(new_cluster), QualityLevel::OPTIMAL);
 2101|   160k|        start_idx = 0;
 2102|   160k|    }
 2103|       |
 2104|       |    // Merge all further Clusters in the group into the result (first one, or new one), and delete
 2105|       |    // them.
 2106|  1.74M|    for (size_t i = start_idx; i < to_merge.size(); ++i) {
  ------------------
  |  Branch (2106:32): [True: 1.53M, False: 215k]
  ------------------
 2107|  1.53M|        into_cluster->Merge(*this, level, *to_merge[i]);
 2108|  1.53M|        DeleteCluster(*to_merge[i], level);
 2109|  1.53M|    }
 2110|   215k|    into_cluster->Compact();
 2111|   215k|    GetClusterSet(level).m_cluster_usage += into_cluster->TotalMemoryUsage();
 2112|   215k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl7CompactEv:
 1776|  2.72M|{
 1777|       |    // We cannot compact while any to-be-applied operations or staged removals remain as we'd need
 1778|       |    // to rewrite them. It is easier to delay the compaction until they have been applied.
 1779|  2.72M|    if (!m_main_clusterset.m_deps_to_add.empty()) return;
  ------------------
  |  Branch (1779:9): [True: 586k, False: 2.13M]
  ------------------
 1780|  2.13M|    if (!m_main_clusterset.m_to_remove.empty()) return;
  ------------------
  |  Branch (1780:9): [True: 5.55k, False: 2.12M]
  ------------------
 1781|  2.12M|    Assume(m_main_clusterset.m_removed.empty()); // non-staging m_removed is always empty
  ------------------
  |  |  128|  2.12M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1782|  2.12M|    if (m_staging_clusterset.has_value()) {
  ------------------
  |  Branch (1782:9): [True: 1.12M, False: 1.00M]
  ------------------
 1783|  1.12M|        if (!m_staging_clusterset->m_deps_to_add.empty()) return;
  ------------------
  |  Branch (1783:13): [True: 509k, False: 618k]
  ------------------
 1784|   618k|        if (!m_staging_clusterset->m_to_remove.empty()) return;
  ------------------
  |  Branch (1784:13): [True: 12.8k, False: 605k]
  ------------------
 1785|   605k|        if (!m_staging_clusterset->m_removed.empty()) return;
  ------------------
  |  Branch (1785:13): [True: 361k, False: 243k]
  ------------------
 1786|   605k|    }
 1787|       |
 1788|       |    // Release memory used by discarded ChunkData index entries.
 1789|  1.24M|    ClearShrink(m_main_chunkindex_discarded);
 1790|       |
 1791|       |    // Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
 1792|       |    // ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
 1793|       |    // later-processed ones during the "swap with end of m_entries" step below (which might
 1794|       |    // invalidate them).
 1795|  1.24M|    std::ranges::sort(m_unlinked, std::greater{});
 1796|       |
 1797|  1.24M|    std::vector<Cluster*> affected_main;
 1798|  1.24M|    auto last = GraphIndex(-1);
 1799|  1.24M|    for (GraphIndex idx : m_unlinked) {
  ------------------
  |  Branch (1799:25): [True: 63.2k, False: 1.24M]
  ------------------
 1800|       |        // m_unlinked should never contain the same GraphIndex twice (the code below would fail
 1801|       |        // if so, because GraphIndexes get invalidated by removing them).
 1802|  63.2k|        Assume(idx != last);
  ------------------
  |  |  128|  63.2k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1803|  63.2k|        last = idx;
 1804|       |
 1805|       |        // Make sure the entry is unlinked.
 1806|  63.2k|        Entry& entry = m_entries[idx];
 1807|  63.2k|        Assume(entry.m_ref == nullptr);
  ------------------
  |  |  128|  63.2k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1808|       |        // Make sure the entry does not occur in the graph.
 1809|   189k|        for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (1809:29): [True: 126k, False: 63.2k]
  ------------------
 1810|   126k|            Assume(!entry.m_locator[level].IsPresent());
  ------------------
  |  |  128|   126k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1811|   126k|        }
 1812|       |
 1813|       |        // Move the entry to the end.
 1814|  63.2k|        if (idx != m_entries.size() - 1) SwapIndexes(idx, m_entries.size() - 1, affected_main);
  ------------------
  |  Branch (1814:13): [True: 42.5k, False: 20.6k]
  ------------------
 1815|       |        // Drop the entry for idx, now that it is at the end.
 1816|  63.2k|        m_entries.pop_back();
 1817|  63.2k|    }
 1818|       |
 1819|       |    // Update the affected clusters, to fixup Entry::m_main_max_chunk_fallback values which may
 1820|       |    // have become outdated due to the compaction above.
 1821|  1.24M|    std::ranges::sort(affected_main);
 1822|  1.24M|    affected_main.erase(std::unique(affected_main.begin(), affected_main.end()), affected_main.end());
 1823|  1.24M|    for (Cluster* cluster : affected_main) {
  ------------------
  |  Branch (1823:27): [True: 12.7k, False: 1.24M]
  ------------------
 1824|  12.7k|        cluster->Updated(*this, /*level=*/0, /*rename=*/true);
 1825|  12.7k|    }
 1826|  1.24M|    m_unlinked.clear();
 1827|  1.24M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl11SwapIndexesEjjRNSt3__16vectorIPNS_7ClusterENS1_9allocatorIS4_EEEE:
 1749|  42.5k|{
 1750|  42.5k|    Assume(a < m_entries.size());
  ------------------
  |  |  128|  42.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1751|  42.5k|    Assume(b < m_entries.size());
  ------------------
  |  |  128|  42.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1752|       |    // Swap the Entry objects.
 1753|  42.5k|    std::swap(m_entries[a], m_entries[b]);
 1754|       |    // Iterate over both objects.
 1755|   127k|    for (int i = 0; i < 2; ++i) {
  ------------------
  |  Branch (1755:21): [True: 85.1k, False: 42.5k]
  ------------------
 1756|  85.1k|        GraphIndex idx = i ? b : a;
  ------------------
  |  Branch (1756:26): [True: 42.5k, False: 42.5k]
  ------------------
 1757|  85.1k|        Entry& entry = m_entries[idx];
 1758|       |        // Update linked Ref, if any exists.
 1759|  85.1k|        if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
  ------------------
  |  Branch (1759:13): [True: 42.5k, False: 42.5k]
  ------------------
 1760|       |        // Update linked chunk index entries, if any exist.
 1761|  85.1k|        if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
  ------------------
  |  Branch (1761:13): [True: 24.2k, False: 60.9k]
  ------------------
 1762|  24.2k|            entry.m_main_chunkindex_iterator->m_graph_index = idx;
 1763|  24.2k|        }
 1764|       |        // Update the locators for both levels.
 1765|   255k|        for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (1765:29): [True: 170k, False: 85.1k]
  ------------------
 1766|   170k|            Locator& locator = entry.m_locator[level];
 1767|   170k|            if (locator.IsPresent()) {
  ------------------
  |  Branch (1767:17): [True: 36.6k, False: 133k]
  ------------------
 1768|  36.6k|                locator.cluster->UpdateMapping(locator.index, idx);
 1769|  36.6k|                if (level == 0) affected_main.push_back(locator.cluster);
  ------------------
  |  Branch (1769:21): [True: 33.7k, False: 2.86k]
  ------------------
 1770|  36.6k|            }
 1771|   170k|        }
 1772|  85.1k|    }
 1773|  42.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl12StartStagingEv:
 2627|  58.7k|{
 2628|       |    // Staging cannot already exist.
 2629|  58.7k|    Assume(!m_staging_clusterset.has_value());
  ------------------
  |  |  128|  58.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2630|       |    // Apply all remaining dependencies in main before creating a staging graph. Once staging
 2631|       |    // exists, we cannot merge Clusters anymore (because of interference with Clusters being
 2632|       |    // pulled into staging), so to make sure all inspectors are available (if not oversized), do
 2633|       |    // all merging work now. Call SplitAll() first, so that even if ApplyDependencies does not do
 2634|       |    // any thing due to knowing the result is oversized, splitting is still performed.
 2635|  58.7k|    SplitAll(0);
 2636|  58.7k|    ApplyDependencies(0);
 2637|       |    // Construct the staging ClusterSet.
 2638|  58.7k|    m_staging_clusterset.emplace();
 2639|       |    // Copy statistics, precomputed data, and to-be-applied dependencies (only if oversized) to
 2640|       |    // the new graph. To-be-applied removals will always be empty at this point.
 2641|  58.7k|    m_staging_clusterset->m_txcount = m_main_clusterset.m_txcount;
 2642|  58.7k|    m_staging_clusterset->m_txcount_oversized = m_main_clusterset.m_txcount_oversized;
 2643|  58.7k|    m_staging_clusterset->m_deps_to_add = m_main_clusterset.m_deps_to_add;
 2644|  58.7k|    m_staging_clusterset->m_group_data = m_main_clusterset.m_group_data;
 2645|  58.7k|    m_staging_clusterset->m_oversized = m_main_clusterset.m_oversized;
 2646|  58.7k|    Assume(m_staging_clusterset->m_oversized.has_value());
  ------------------
  |  |  128|  58.7k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2647|  58.7k|    m_staging_clusterset->m_cluster_usage = 0;
 2648|  58.7k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl12AbortStagingEv:
 2651|  46.9k|{
 2652|       |    // Staging must exist.
 2653|  46.9k|    Assume(m_staging_clusterset.has_value());
  ------------------
  |  |  128|  46.9k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2654|       |    // Mark all removed transactions as Missing (so the staging locator for these transactions
 2655|       |    // can be reused if another staging is created).
 2656|   390k|    for (auto idx : m_staging_clusterset->m_removed) {
  ------------------
  |  Branch (2656:19): [True: 390k, False: 46.9k]
  ------------------
 2657|   390k|        m_entries[idx].m_locator[1].SetMissing();
 2658|   390k|    }
 2659|       |    // Do the same with the non-removed transactions in staging Clusters.
 2660|   375k|    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
  ------------------
  |  Branch (2660:27): [True: 328k, False: 46.9k]
  ------------------
 2661|   328k|        for (auto& cluster : m_staging_clusterset->m_clusters[quality]) {
  ------------------
  |  Branch (2661:28): [True: 139k, False: 328k]
  ------------------
 2662|   139k|            cluster->MakeStagingTransactionsMissing(*this);
 2663|   139k|        }
 2664|   328k|    }
 2665|       |    // Destroy the staging ClusterSet.
 2666|  46.9k|    m_staging_clusterset.reset();
 2667|  46.9k|    Compact();
 2668|  46.9k|    if (!m_main_clusterset.m_group_data.has_value()) {
  ------------------
  |  Branch (2668:9): [True: 9.99k, False: 36.9k]
  ------------------
 2669|       |        // In case m_oversized in main was kept after a Ref destruction while staging exists, we
 2670|       |        // need to re-evaluate m_oversized now.
 2671|  9.99k|        if (m_main_clusterset.m_to_remove.empty() && m_main_clusterset.m_txcount_oversized > 0) {
  ------------------
  |  Branch (2671:13): [True: 9.75k, False: 236]
  |  Branch (2671:54): [True: 9.28k, False: 468]
  ------------------
 2672|       |            // It is possible that a Ref destruction caused a removal in main while staging existed.
 2673|       |            // In this case, m_txcount_oversized may be inaccurate.
 2674|  9.28k|            m_main_clusterset.m_oversized = true;
 2675|  9.28k|        } else {
 2676|    704|            m_main_clusterset.m_oversized = std::nullopt;
 2677|    704|        }
 2678|  9.99k|    }
 2679|  46.9k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl13CommitStagingEv:
 2682|  4.35k|{
 2683|       |    // Staging must exist.
 2684|  4.35k|    Assume(m_staging_clusterset.has_value());
  ------------------
  |  |  128|  4.35k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2685|  4.35k|    Assume(m_main_chunkindex_observers == 0);
  ------------------
  |  |  128|  4.35k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2686|       |    // Get rid of removed transactions in staging before moving to main, so they do not need to be
 2687|       |    // added to the chunk index there. Doing so is impossible if they were unlinked, and thus have
 2688|       |    // no Ref anymore to pass to the fallback comparator.
 2689|  4.35k|    ApplyRemovals(/*up_to_level=*/1);
 2690|       |    // Delete all conflicting Clusters in main, to make place for moving the staging ones
 2691|       |    // there. All of these have been copied to staging in PullIn().
 2692|  4.35k|    auto conflicts = GetConflicts();
 2693|  19.0k|    for (Cluster* conflict : conflicts) {
  ------------------
  |  Branch (2693:28): [True: 19.0k, False: 4.35k]
  ------------------
 2694|  19.0k|        conflict->Clear(*this, /*level=*/0);
 2695|  19.0k|        DeleteCluster(*conflict, /*level=*/0);
 2696|  19.0k|    }
 2697|       |    // Mark the removed transactions as Missing (so the staging locator for these transactions
 2698|       |    // can be reused if another staging is created).
 2699|  9.93k|    for (auto idx : m_staging_clusterset->m_removed) {
  ------------------
  |  Branch (2699:19): [True: 9.93k, False: 4.35k]
  ------------------
 2700|  9.93k|        m_entries[idx].m_locator[1].SetMissing();
 2701|  9.93k|    }
 2702|       |    // Then move all Clusters in staging to main.
 2703|  34.8k|    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
  ------------------
  |  Branch (2703:27): [True: 30.4k, False: 4.35k]
  ------------------
 2704|  30.4k|        auto& stage_sets = m_staging_clusterset->m_clusters[quality];
 2705|  52.5k|        while (!stage_sets.empty()) {
  ------------------
  |  Branch (2705:16): [True: 22.0k, False: 30.4k]
  ------------------
 2706|  22.0k|            stage_sets.back()->MoveToMain(*this);
 2707|  22.0k|        }
 2708|  30.4k|    }
 2709|       |    // Move all statistics, precomputed data, and to-be-applied removals and dependencies.
 2710|  4.35k|    m_main_clusterset.m_deps_to_add = std::move(m_staging_clusterset->m_deps_to_add);
 2711|  4.35k|    m_main_clusterset.m_to_remove = std::move(m_staging_clusterset->m_to_remove);
 2712|  4.35k|    m_main_clusterset.m_group_data = std::move(m_staging_clusterset->m_group_data);
 2713|  4.35k|    m_main_clusterset.m_oversized = m_staging_clusterset->m_oversized;
 2714|  4.35k|    m_main_clusterset.m_txcount = m_staging_clusterset->m_txcount;
 2715|  4.35k|    m_main_clusterset.m_txcount_oversized = m_staging_clusterset->m_txcount_oversized;
 2716|       |    // Delete the old staging graph, after all its information was moved to main.
 2717|  4.35k|    m_staging_clusterset.reset();
 2718|  4.35k|    Compact();
 2719|  4.35k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl12GetConflictsEv:
 1197|  47.8k|{
 1198|  47.8k|    Assume(GetTopLevel() == 1);
  ------------------
  |  |  128|  47.8k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 1199|  47.8k|    auto& clusterset = GetClusterSet(1);
 1200|  47.8k|    std::vector<Cluster*> ret;
 1201|       |    // All main Clusters containing transactions in m_removed (so (P,R) ones) are conflicts.
 1202|   297k|    for (auto i : clusterset.m_removed) {
  ------------------
  |  Branch (1202:17): [True: 297k, False: 47.8k]
  ------------------
 1203|   297k|        auto& entry = m_entries[i];
 1204|   297k|        if (entry.m_locator[0].IsPresent()) {
  ------------------
  |  Branch (1204:13): [True: 296k, False: 1.53k]
  ------------------
 1205|   296k|            ret.push_back(entry.m_locator[0].cluster);
 1206|   296k|        }
 1207|   297k|    }
 1208|       |    // Then go over all Clusters at this level, and find their conflicts (the (P,P) ones).
 1209|   382k|    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
  ------------------
  |  Branch (1209:27): [True: 335k, False: 47.8k]
  ------------------
 1210|   335k|        auto& clusters = clusterset.m_clusters[quality];
 1211|   335k|        for (const auto& cluster : clusters) {
  ------------------
  |  Branch (1211:34): [True: 286k, False: 335k]
  ------------------
 1212|   286k|            cluster->GetConflicts(*this, ret);
 1213|   286k|        }
 1214|   335k|    }
 1215|       |    // Deduplicate the result (the same Cluster may appear multiple times).
 1216|  47.8k|    std::ranges::sort(ret, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
 1217|  47.8k|    ret.erase(std::ranges::unique(ret).begin(), ret.end());
 1218|  47.8k|    return ret;
 1219|  47.8k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl13GetClusterSetEi:
  844|  98.6k|{
  845|  98.6k|    if (level == 0) return m_main_clusterset;
  ------------------
  |  Branch (845:9): [True: 36.0k, False: 62.6k]
  ------------------
  846|  62.6k|    Assume(level == 1);
  ------------------
  |  |  128|  62.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  847|  62.6k|    Assume(m_staging_clusterset.has_value());
  ------------------
  |  |  128|  62.6k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  848|  62.6k|    return *m_staging_clusterset;
  849|  98.6k|}
txgraph.cpp:_ZZNK12_GLOBAL__N_111TxGraphImpl12GetConflictsEvENK3$_0clEPNS_7ClusterES3_:
 1216|  98.6M|    std::ranges::sort(ret, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl11HaveStagingEv:
  810|  31.6k|    bool HaveStaging() const noexcept final { return m_staging_clusterset.has_value(); }
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl11IsOversizedEN7TxGraph5LevelE:
 2607|  66.1k|{
 2608|  66.1k|    size_t level = GetSpecifiedLevel(level_select);
 2609|  66.1k|    auto& clusterset = GetClusterSet(level);
 2610|  66.1k|    if (clusterset.m_oversized.has_value()) {
  ------------------
  |  Branch (2610:9): [True: 53.8k, False: 12.2k]
  ------------------
 2611|       |        // Return cached value if known.
 2612|  53.8k|        return *clusterset.m_oversized;
 2613|  53.8k|    }
 2614|  12.2k|    ApplyRemovals(level);
 2615|  12.2k|    if (clusterset.m_txcount_oversized > 0) {
  ------------------
  |  Branch (2615:9): [True: 989, False: 11.2k]
  ------------------
 2616|    989|        clusterset.m_oversized = true;
 2617|  11.2k|    } else {
 2618|       |        // Find which Clusters will need to be merged together, as that is where the oversize
 2619|       |        // property is assessed.
 2620|  11.2k|        GroupClusters(level);
 2621|  11.2k|    }
 2622|  12.2k|    Assume(clusterset.m_oversized.has_value());
  ------------------
  |  |  128|  12.2k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2623|  12.2k|    return *clusterset.m_oversized;
 2624|  66.1k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl17GetSpecifiedLevelEN7TxGraph5LevelE:
  681|  3.82M|    int GetSpecifiedLevel(Level level) const noexcept { return level == Level::TOP && m_staging_clusterset.has_value(); }
  ------------------
  |  Branch (681:64): [True: 1.84M, False: 1.97M]
  |  Branch (681:87): [True: 965k, False: 884k]
  ------------------
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl6ExistsERKN7TxGraph3RefENS1_5LevelE:
 2306|  8.30k|{
 2307|  8.30k|    if (GetRefGraph(arg) == nullptr) return false;
  ------------------
  |  Branch (2307:9): [True: 1.03k, False: 7.26k]
  ------------------
 2308|  7.26k|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|  7.26k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2309|  7.26k|    size_t level = GetSpecifiedLevel(level_select);
 2310|       |    // Make sure the transaction isn't scheduled for removal.
 2311|  7.26k|    ApplyRemovals(level);
 2312|  7.26k|    auto cluster = FindCluster(GetRefIndex(arg), level);
 2313|  7.26k|    return cluster != nullptr;
 2314|  8.30k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl20GetIndividualFeerateERKN7TxGraph3RefE:
 2565|  1.44M|{
 2566|       |    // Return the empty FeePerWeight if the passed Ref is empty.
 2567|  1.44M|    if (GetRefGraph(arg) == nullptr) return {};
  ------------------
  |  Branch (2567:9): [True: 1.56k, False: 1.44M]
  ------------------
 2568|  1.44M|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|  1.44M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2569|       |    // Find the cluster the argument is in (the level does not matter as individual feerates will
 2570|       |    // be identical if it occurs in both), and return the empty FeePerWeight if it isn't in any.
 2571|  1.44M|    Cluster* cluster{nullptr};
 2572|  1.44M|    int level;
 2573|  1.47M|    for (level = 0; level <= GetTopLevel(); ++level) {
  ------------------
  |  Branch (2573:21): [True: 1.47M, False: 685]
  ------------------
 2574|       |        // Apply removals, so that we can correctly report FeePerWeight{} for non-existing
 2575|       |        // transactions.
 2576|  1.47M|        ApplyRemovals(level);
 2577|  1.47M|        if (m_entries[GetRefIndex(arg)].m_locator[level].IsPresent()) {
  ------------------
  |  Branch (2577:13): [True: 1.44M, False: 34.6k]
  ------------------
 2578|  1.44M|            cluster = m_entries[GetRefIndex(arg)].m_locator[level].cluster;
 2579|  1.44M|            break;
 2580|  1.44M|        }
 2581|  1.47M|    }
 2582|  1.44M|    if (cluster == nullptr) return {};
  ------------------
  |  Branch (2582:9): [True: 685, False: 1.44M]
  ------------------
 2583|       |    // Dispatch to the Cluster.
 2584|  1.44M|    return cluster->GetIndividualFeerate(m_entries[GetRefIndex(arg)].m_locator[level].index);
 2585|  1.44M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl19GetMainChunkFeerateERKN7TxGraph3RefE:
 2588|  15.5M|{
 2589|       |    // Return the empty FeePerWeight if the passed Ref is empty.
 2590|  15.5M|    if (GetRefGraph(arg) == nullptr) return {};
  ------------------
  |  Branch (2590:9): [True: 1.04k, False: 15.5M]
  ------------------
 2591|  15.5M|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|  15.5M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2592|       |    // Apply all removals and dependencies, as the result might be inaccurate otherwise.
 2593|  15.5M|    ApplyDependencies(/*level=*/0);
 2594|       |    // Chunk feerates cannot be accurately known if unapplied dependencies remain.
 2595|  15.5M|    Assume(m_main_clusterset.m_deps_to_add.empty());
  ------------------
  |  |  128|  15.5M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2596|       |    // Find the cluster the argument is in, and return the empty FeePerWeight if it isn't in any.
 2597|  15.5M|    auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), /*level=*/0);
 2598|  15.5M|    if (cluster == nullptr) return {};
  ------------------
  |  Branch (2598:9): [True: 789, False: 15.5M]
  ------------------
 2599|       |    // Make sure the Cluster has an acceptable quality level, and then return the transaction's
 2600|       |    // chunk feerate.
 2601|  15.5M|    MakeAcceptable(*cluster, cluster_level);
 2602|  15.5M|    const auto& entry = m_entries[GetRefIndex(arg)];
 2603|  15.5M|    return entry.m_main_chunk_feerate;
 2604|  15.5M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl10GetClusterERKN7TxGraph3RefENS1_5LevelE:
 2537|  1.63M|{
 2538|       |    // Return the empty vector if the Ref is empty (which may be indicative of the transaction
 2539|       |    // having been removed already.
 2540|  1.63M|    if (GetRefGraph(arg) == nullptr) return {};
  ------------------
  |  Branch (2540:9): [True: 1.85k, False: 1.63M]
  ------------------
 2541|  1.63M|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|  1.63M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2542|       |    // Apply all removals and dependencies, as the result might be incorrect otherwise.
 2543|  1.63M|    size_t level = GetSpecifiedLevel(level_select);
 2544|  1.63M|    ApplyDependencies(level);
 2545|       |    // Cluster linearization cannot be known if unapplied dependencies remain.
 2546|  1.63M|    Assume(GetClusterSet(level).m_deps_to_add.empty());
  ------------------
  |  |  128|  1.63M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2547|       |    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
 2548|  1.63M|    auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
 2549|  1.63M|    if (cluster == nullptr) return {};
  ------------------
  |  Branch (2549:9): [True: 1.47k, False: 1.63M]
  ------------------
 2550|       |    // Make sure the Cluster has an acceptable quality level, and then dispatch to it.
 2551|  1.63M|    MakeAcceptable(*cluster, cluster_level);
 2552|  1.63M|    std::vector<TxGraph::Ref*> ret(cluster->GetTxCount());
 2553|  1.63M|    cluster->GetClusterRefs(*this, ret, 0);
 2554|  1.63M|    return ret;
 2555|  1.63M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl12GetAncestorsERKN7TxGraph3RefENS1_5LevelE:
 2429|   965k|{
 2430|       |    // Return the empty vector if the Ref is empty.
 2431|   965k|    if (GetRefGraph(arg) == nullptr) return {};
  ------------------
  |  Branch (2431:9): [True: 847, False: 965k]
  ------------------
 2432|   965k|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|   965k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2433|       |    // Apply all removals and dependencies, as the result might be incorrect otherwise.
 2434|   965k|    size_t level = GetSpecifiedLevel(level_select);
 2435|   965k|    ApplyDependencies(level);
 2436|       |    // Ancestry cannot be known if unapplied dependencies remain.
 2437|   965k|    Assume(GetClusterSet(level).m_deps_to_add.empty());
  ------------------
  |  |  128|   965k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2438|       |    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
 2439|   965k|    auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
 2440|   965k|    if (cluster == nullptr) return {};
  ------------------
  |  Branch (2440:9): [True: 432, False: 964k]
  ------------------
 2441|       |    // Dispatch to the Cluster.
 2442|   964k|    std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster_level].index};
 2443|   964k|    auto matches = std::span(&match, 1);
 2444|   964k|    std::vector<TxGraph::Ref*> ret;
 2445|   964k|    cluster->GetAncestorRefs(*this, matches, ret);
 2446|   964k|    return ret;
 2447|   965k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl14GetDescendantsERKN7TxGraph3RefENS1_5LevelE:
 2450|   966k|{
 2451|       |    // Return the empty vector if the Ref is empty.
 2452|   966k|    if (GetRefGraph(arg) == nullptr) return {};
  ------------------
  |  Branch (2452:9): [True: 624, False: 965k]
  ------------------
 2453|   965k|    Assume(GetRefGraph(arg) == this);
  ------------------
  |  |  128|   965k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2454|       |    // Apply all removals and dependencies, as the result might be incorrect otherwise.
 2455|   965k|    size_t level = GetSpecifiedLevel(level_select);
 2456|   965k|    ApplyDependencies(level);
 2457|       |    // Ancestry cannot be known if unapplied dependencies remain.
 2458|   965k|    Assume(GetClusterSet(level).m_deps_to_add.empty());
  ------------------
  |  |  128|   965k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2459|       |    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
 2460|   965k|    auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
 2461|   965k|    if (cluster == nullptr) return {};
  ------------------
  |  Branch (2461:9): [True: 743, False: 964k]
  ------------------
 2462|       |    // Dispatch to the Cluster.
 2463|   964k|    std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster_level].index};
 2464|   964k|    auto matches = std::span(&match, 1);
 2465|   964k|    std::vector<TxGraph::Ref*> ret;
 2466|   964k|    cluster->GetDescendantRefs(*this, matches, ret);
 2467|   964k|    return ret;
 2468|   965k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17GetAncestorsUnionENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelE:
 2471|  10.4k|{
 2472|       |    // Apply all dependencies, as the result might be incorrect otherwise.
 2473|  10.4k|    size_t level = GetSpecifiedLevel(level_select);
 2474|  10.4k|    ApplyDependencies(level);
 2475|       |    // Ancestry cannot be known if unapplied dependencies remain.
 2476|  10.4k|    Assume(GetClusterSet(level).m_deps_to_add.empty());
  ------------------
  |  |  128|  10.4k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2477|       |
 2478|       |    // Translate args to matches.
 2479|  10.4k|    std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
 2480|  10.4k|    matches.reserve(args.size());
 2481|  50.9k|    for (auto arg : args) {
  ------------------
  |  Branch (2481:19): [True: 50.9k, False: 10.4k]
  ------------------
 2482|  50.9k|        Assume(arg);
  ------------------
  |  |  128|  50.9k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2483|       |        // Skip empty Refs.
 2484|  50.9k|        if (GetRefGraph(*arg) == nullptr) continue;
  ------------------
  |  Branch (2484:13): [True: 8.18k, False: 42.8k]
  ------------------
 2485|  42.8k|        Assume(GetRefGraph(*arg) == this);
  ------------------
  |  |  128|  42.8k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2486|       |        // Find the Cluster the argument is in, and skip if none is found.
 2487|  42.8k|        auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(*arg), level);
 2488|  42.8k|        if (cluster == nullptr) continue;
  ------------------
  |  Branch (2488:13): [True: 2.81k, False: 40.0k]
  ------------------
 2489|       |        // Append to matches.
 2490|  40.0k|        matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
 2491|  40.0k|    }
 2492|       |    // Group by Cluster.
 2493|  10.4k|    std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
 2494|       |    // Dispatch to the Clusters.
 2495|  10.4k|    std::span match_span(matches);
 2496|  10.4k|    std::vector<TxGraph::Ref*> ret;
 2497|  31.5k|    while (!match_span.empty()) {
  ------------------
  |  Branch (2497:12): [True: 21.0k, False: 10.4k]
  ------------------
 2498|  21.0k|        match_span.front().first->GetAncestorRefs(*this, match_span, ret);
 2499|  21.0k|    }
 2500|  10.4k|    return ret;
 2501|  10.4k|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl17GetAncestorsUnionENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelEENK3$_0clINS1_4pairIPNS_7ClusterEjEESF_EEDaRT_RT0_:
 2493|   435k|    std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl19GetDescendantsUnionENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelE:
 2504|  10.2k|{
 2505|       |    // Apply all dependencies, as the result might be incorrect otherwise.
 2506|  10.2k|    size_t level = GetSpecifiedLevel(level_select);
 2507|  10.2k|    ApplyDependencies(level);
 2508|       |    // Ancestry cannot be known if unapplied dependencies remain.
 2509|  10.2k|    Assume(GetClusterSet(level).m_deps_to_add.empty());
  ------------------
  |  |  128|  10.2k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2510|       |
 2511|       |    // Translate args to matches.
 2512|  10.2k|    std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
 2513|  10.2k|    matches.reserve(args.size());
 2514|  54.0k|    for (auto arg : args) {
  ------------------
  |  Branch (2514:19): [True: 54.0k, False: 10.2k]
  ------------------
 2515|  54.0k|        Assume(arg);
  ------------------
  |  |  128|  54.0k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2516|       |        // Skip empty Refs.
 2517|  54.0k|        if (GetRefGraph(*arg) == nullptr) continue;
  ------------------
  |  Branch (2517:13): [True: 11.7k, False: 42.3k]
  ------------------
 2518|  42.3k|        Assume(GetRefGraph(*arg) == this);
  ------------------
  |  |  128|  42.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2519|       |        // Find the Cluster the argument is in, and skip if none is found.
 2520|  42.3k|        auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(*arg), level);
 2521|  42.3k|        if (cluster == nullptr) continue;
  ------------------
  |  Branch (2521:13): [True: 2.52k, False: 39.7k]
  ------------------
 2522|       |        // Append to matches.
 2523|  39.7k|        matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
 2524|  39.7k|    }
 2525|       |    // Group by Cluster.
 2526|  10.2k|    std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
 2527|       |    // Dispatch to the Clusters.
 2528|  10.2k|    std::span match_span(matches);
 2529|  10.2k|    std::vector<TxGraph::Ref*> ret;
 2530|  31.4k|    while (!match_span.empty()) {
  ------------------
  |  Branch (2530:12): [True: 21.2k, False: 10.2k]
  ------------------
 2531|  21.2k|        match_span.front().first->GetDescendantRefs(*this, match_span, ret);
 2532|  21.2k|    }
 2533|  10.2k|    return ret;
 2534|  10.2k|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl19GetDescendantsUnionENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelEENK3$_0clINS1_4pairIPNS_7ClusterEjEESF_EEDaRT_RT0_:
 2526|   436k|    std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl19GetTransactionCountEN7TxGraph5LevelE:
 2558|  50.2k|{
 2559|  50.2k|    size_t level = GetSpecifiedLevel(level_select);
 2560|  50.2k|    ApplyRemovals(level);
 2561|  50.2k|    return GetClusterSet(level).m_txcount;
 2562|  50.2k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl16CompareMainOrderERKN7TxGraph3RefES4_:
 2763|  73.0M|{
 2764|       |    // The references must not be empty.
 2765|  73.0M|    Assume(GetRefGraph(a) == this);
  ------------------
  |  |  128|  73.0M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2766|  73.0M|    Assume(GetRefGraph(b) == this);
  ------------------
  |  |  128|  73.0M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2767|       |    // Apply dependencies in main.
 2768|  73.0M|    ApplyDependencies(0);
 2769|  73.0M|    Assume(m_main_clusterset.m_deps_to_add.empty());
  ------------------
  |  |  128|  73.0M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2770|       |    // Make both involved Clusters acceptable, so chunk feerates are relevant.
 2771|  73.0M|    const auto& entry_a = m_entries[GetRefIndex(a)];
 2772|  73.0M|    const auto& entry_b = m_entries[GetRefIndex(b)];
 2773|  73.0M|    const auto& locator_a = entry_a.m_locator[0];
 2774|  73.0M|    const auto& locator_b = entry_b.m_locator[0];
 2775|  73.0M|    Assume(locator_a.IsPresent());
  ------------------
  |  |  128|  73.0M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2776|  73.0M|    Assume(locator_b.IsPresent());
  ------------------
  |  |  128|  73.0M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2777|  73.0M|    MakeAcceptable(*locator_a.cluster, /*level=*/0);
 2778|  73.0M|    MakeAcceptable(*locator_b.cluster, /*level=*/0);
 2779|       |    // Invoke comparison logic.
 2780|  73.0M|    return CompareMainTransactions(GetRefIndex(a), GetRefIndex(b));
 2781|  73.0M|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl21CountDistinctClustersENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelE:
 2784|   109k|{
 2785|   109k|    size_t level = GetSpecifiedLevel(level_select);
 2786|   109k|    ApplyDependencies(level);
 2787|   109k|    auto& clusterset = GetClusterSet(level);
 2788|   109k|    Assume(clusterset.m_deps_to_add.empty());
  ------------------
  |  |  128|   109k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2789|       |    // Build a vector of Clusters that the specified Refs occur in.
 2790|   109k|    std::vector<Cluster*> clusters;
 2791|   109k|    clusters.reserve(refs.size());
 2792|   631k|    for (const Ref* ref : refs) {
  ------------------
  |  Branch (2792:25): [True: 631k, False: 109k]
  ------------------
 2793|   631k|        Assume(ref);
  ------------------
  |  |  128|   631k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2794|   631k|        if (GetRefGraph(*ref) == nullptr) continue;
  ------------------
  |  Branch (2794:13): [True: 84.5k, False: 547k]
  ------------------
 2795|   547k|        Assume(GetRefGraph(*ref) == this);
  ------------------
  |  |  128|   547k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2796|   547k|        auto cluster = FindCluster(GetRefIndex(*ref), level);
 2797|   547k|        if (cluster != nullptr) clusters.push_back(cluster);
  ------------------
  |  Branch (2797:13): [True: 526k, False: 20.5k]
  ------------------
 2798|   547k|    }
 2799|       |    // Count the number of distinct elements in clusters.
 2800|   109k|    std::ranges::sort(clusters, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
 2801|   109k|    Cluster* last{nullptr};
 2802|   109k|    GraphIndex ret{0};
 2803|   526k|    for (Cluster* cluster : clusters) {
  ------------------
  |  Branch (2803:27): [True: 526k, False: 109k]
  ------------------
 2804|   526k|        ret += (cluster != last);
 2805|   526k|        last = cluster;
 2806|   526k|    }
 2807|   109k|    return ret;
 2808|   109k|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl21CountDistinctClustersENSt3__14spanIKPKN7TxGraph3RefELm18446744073709551615EEENS3_5LevelEENK3$_0clEPNS_7ClusterESC_:
 2800|  42.5M|    std::ranges::sort(clusters, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl22GetMainStagingDiagramsEv:
 2811|  43.5k|{
 2812|  43.5k|    Assume(m_staging_clusterset.has_value());
  ------------------
  |  |  128|  43.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2813|  43.5k|    MakeAllAcceptable(0);
 2814|  43.5k|    Assume(m_main_clusterset.m_deps_to_add.empty()); // can only fail if main is oversized
  ------------------
  |  |  128|  43.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2815|  43.5k|    MakeAllAcceptable(1);
 2816|  43.5k|    Assume(m_staging_clusterset->m_deps_to_add.empty()); // can only fail if staging is oversized
  ------------------
  |  |  128|  43.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 2817|       |    // For all Clusters in main which conflict with Clusters in staging (i.e., all that are removed
 2818|       |    // by, or replaced in, staging), gather their chunk feerates.
 2819|  43.5k|    auto main_clusters = GetConflicts();
 2820|  43.5k|    std::vector<FeeFrac> main_feerates, staging_feerates;
 2821|  1.12M|    for (Cluster* cluster : main_clusters) {
  ------------------
  |  Branch (2821:27): [True: 1.12M, False: 43.5k]
  ------------------
 2822|  1.12M|        cluster->AppendChunkFeerates(main_feerates);
 2823|  1.12M|    }
 2824|       |    // Do the same for the Clusters in staging themselves.
 2825|   348k|    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
  ------------------
  |  Branch (2825:27): [True: 304k, False: 43.5k]
  ------------------
 2826|   304k|        for (const auto& cluster : m_staging_clusterset->m_clusters[quality]) {
  ------------------
  |  Branch (2826:34): [True: 264k, False: 304k]
  ------------------
 2827|   264k|            cluster->AppendChunkFeerates(staging_feerates);
 2828|   264k|        }
 2829|   304k|    }
 2830|       |    // Sort both by decreasing feerate to obtain diagrams, and return them.
 2831|  43.5k|    std::ranges::sort(main_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
 2832|  43.5k|    std::ranges::sort(staging_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
 2833|  43.5k|    return std::make_pair(std::move(main_feerates), std::move(staging_feerates));
 2834|  43.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17MakeAllAcceptableEi:
 2216|   141k|{
 2217|   141k|    ApplyDependencies(level);
 2218|   141k|    auto& clusterset = GetClusterSet(level);
 2219|   141k|    if (clusterset.m_oversized == true) return;
  ------------------
  |  Branch (2219:9): [True: 0, False: 141k]
  ------------------
 2220|   282k|    for (auto quality : {QualityLevel::NEEDS_FIX, QualityLevel::NEEDS_RELINEARIZE}) {
  ------------------
  |  Branch (2220:23): [True: 282k, False: 141k]
  ------------------
 2221|   282k|        auto& queue = clusterset.m_clusters[int(quality)];
 2222|   340k|        while (!queue.empty()) {
  ------------------
  |  Branch (2222:16): [True: 58.2k, False: 282k]
  ------------------
 2223|  58.2k|            MakeAcceptable(*queue.back().get(), level);
 2224|  58.2k|        }
 2225|   282k|    }
 2226|   141k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl4TrimEv:
 3299|   131k|{
 3300|   131k|    int level = GetTopLevel();
 3301|   131k|    Assume(m_main_chunkindex_observers == 0 || level != 0);
  ------------------
  |  |  128|   132k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 129k, False: 1.47k]
  |  |  |  Branch (128:51): [True: 1.47k, False: 0]
  |  |  ------------------
  ------------------
 3302|   131k|    std::vector<TxGraph::Ref*> ret;
 3303|       |
 3304|       |    // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
 3305|   131k|    auto& clusterset = GetClusterSet(level);
 3306|   131k|    if (clusterset.m_oversized == false) return ret;
  ------------------
  |  Branch (3306:9): [True: 16.3k, False: 114k]
  ------------------
 3307|   114k|    GroupClusters(level);
 3308|   114k|    Assume(clusterset.m_group_data.has_value());
  ------------------
  |  |  128|   114k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3309|       |    // Nothing to do if not oversized.
 3310|   114k|    Assume(clusterset.m_oversized.has_value());
  ------------------
  |  |  128|   114k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3311|   114k|    if (clusterset.m_oversized == false) return ret;
  ------------------
  |  Branch (3311:9): [True: 658, False: 114k]
  ------------------
 3312|       |
 3313|       |    // In this function, would-be clusters (as precomputed in m_group_data by GroupClusters) are
 3314|       |    // trimmed by removing transactions in them such that the resulting clusters satisfy the size
 3315|       |    // and count limits.
 3316|       |    //
 3317|       |    // It works by defining for each would-be cluster a rudimentary linearization: at every point
 3318|       |    // the highest-chunk-feerate remaining transaction is picked among those with no unmet
 3319|       |    // dependencies. "Dependency" here means either a to-be-added dependency (m_deps_to_add), or
 3320|       |    // an implicit dependency added between any two consecutive transaction in their current
 3321|       |    // cluster linearization. So it can be seen as a "merge sort" of the chunks of the clusters,
 3322|       |    // but respecting the dependencies being added.
 3323|       |    //
 3324|       |    // This rudimentary linearization is computed lazily, by putting all eligible (no unmet
 3325|       |    // dependencies) transactions in a heap, and popping the highest-feerate one from it. Along the
 3326|       |    // way, the counts and sizes of the would-be clusters up to that point are tracked (by
 3327|       |    // partitioning the involved transactions using a union-find structure). Any transaction whose
 3328|       |    // addition would cause a violation is removed, along with all their descendants.
 3329|       |    //
 3330|       |    // A next invocation of GroupClusters (after applying the removals) will compute the new
 3331|       |    // resulting clusters, and none of them will violate the limits.
 3332|       |
 3333|       |    /** All dependencies (both to be added ones, and implicit ones between consecutive transactions
 3334|       |     *  in existing cluster linearizations), sorted by parent. */
 3335|   114k|    std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_parent;
 3336|       |    /** Same, but sorted by child. */
 3337|   114k|    std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_child;
 3338|       |    /** Information about all transactions involved in a Cluster group to be trimmed, sorted by
 3339|       |     *  GraphIndex. It contains entries both for transactions that have already been included,
 3340|       |     *  and ones that have not yet been. */
 3341|   114k|    std::vector<TrimTxData> trim_data;
 3342|       |    /** Iterators into trim_data, treated as a max heap according to cmp_fn below. Each entry is
 3343|       |     *  a transaction that has not yet been included yet, but all its ancestors have. */
 3344|   114k|    std::vector<std::vector<TrimTxData>::iterator> trim_heap;
 3345|       |    /** The list of representatives of the partitions a given transaction depends on. */
 3346|   114k|    std::vector<TrimTxData*> current_deps;
 3347|       |
 3348|       |    /** Function to define the ordering of trim_heap. */
 3349|   114k|    static constexpr auto cmp_fn = [](auto a, auto b) noexcept {
 3350|       |        // Sort by increasing chunk feerate, and then by decreasing size.
 3351|       |        // We do not need to sort by cluster or within clusters, because due to the implicit
 3352|       |        // dependency between consecutive linearization elements, no two transactions from the
 3353|       |        // same Cluster will ever simultaneously be in the heap.
 3354|   114k|        return ByRatioNegSize{a->m_chunk_feerate} < ByRatioNegSize{b->m_chunk_feerate};
 3355|   114k|    };
 3356|       |
 3357|       |    /** Given a TrimTxData entry, find the representative of the partition it is in. */
 3358|   114k|    static constexpr auto find_fn = [](TrimTxData* arg) noexcept {
 3359|   114k|        while (arg != arg->m_uf_parent) {
 3360|       |            // Replace pointer to parent with pointer to grandparent (path splitting).
 3361|       |            // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
 3362|   114k|            auto par = arg->m_uf_parent;
 3363|   114k|            arg->m_uf_parent = par->m_uf_parent;
 3364|   114k|            arg = par;
 3365|   114k|        }
 3366|   114k|        return arg;
 3367|   114k|    };
 3368|       |
 3369|       |    /** Given two TrimTxData entries, union the partitions they are in, and return the
 3370|       |     *  representative. */
 3371|   114k|    static constexpr auto union_fn = [](TrimTxData* arg1, TrimTxData* arg2) noexcept {
 3372|       |        // Replace arg1 and arg2 by their representatives.
 3373|   114k|        auto rep1 = find_fn(arg1);
 3374|   114k|        auto rep2 = find_fn(arg2);
 3375|       |        // Bail out if both representatives are the same, because that means arg1 and arg2 are in
 3376|       |        // the same partition already.
 3377|   114k|        if (rep1 == rep2) return rep1;
 3378|       |        // Pick the lower-count root to become a child of the higher-count one.
 3379|       |        // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_size.
 3380|   114k|        if (rep1->m_uf_count < rep2->m_uf_count) std::swap(rep1, rep2);
 3381|   114k|        rep2->m_uf_parent = rep1;
 3382|       |        // Add the statistics of arg2 (which is no longer a representative) to those of arg1 (which
 3383|       |        // is now the representative for both).
 3384|   114k|        rep1->m_uf_size += rep2->m_uf_size;
 3385|   114k|        rep1->m_uf_count += rep2->m_uf_count;
 3386|   114k|        return rep1;
 3387|   114k|    };
 3388|       |
 3389|       |    /** Get iterator to TrimTxData entry for a given index. */
 3390|   114k|    auto locate_fn = [&](GraphIndex index) noexcept {
 3391|   114k|        auto it = std::lower_bound(trim_data.begin(), trim_data.end(), index, [](TrimTxData& elem, GraphIndex idx) noexcept {
 3392|   114k|            return elem.m_index < idx;
 3393|   114k|        });
 3394|   114k|        Assume(it != trim_data.end() && it->m_index == index);
 3395|   114k|        return it;
 3396|   114k|    };
 3397|       |
 3398|       |    // For each group of to-be-merged Clusters.
 3399|   383k|    for (const auto& group_data : clusterset.m_group_data->m_groups) {
  ------------------
  |  Branch (3399:33): [True: 383k, False: 114k]
  ------------------
 3400|   383k|        trim_data.clear();
 3401|   383k|        trim_heap.clear();
 3402|   383k|        deps_by_child.clear();
 3403|   383k|        deps_by_parent.clear();
 3404|       |
 3405|       |        // Gather trim data and implicit dependency data from all involved Clusters.
 3406|   383k|        auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
 3407|   383k|                                .subspan(group_data.m_cluster_offset, group_data.m_cluster_count);
 3408|   383k|        uint64_t size{0};
 3409|  2.55M|        for (Cluster* cluster : cluster_span) {
  ------------------
  |  Branch (3409:31): [True: 2.55M, False: 383k]
  ------------------
 3410|  2.55M|            MakeAcceptable(*cluster, cluster->GetLevel(*this));
 3411|  2.55M|            size += cluster->AppendTrimData(trim_data, deps_by_child);
 3412|  2.55M|        }
 3413|       |        // If this group of Clusters does not violate any limits, continue to the next group.
 3414|   383k|        if (trim_data.size() <= m_max_cluster_count && size <= m_max_cluster_size) continue;
  ------------------
  |  Branch (3414:13): [True: 225k, False: 158k]
  |  Branch (3414:56): [True: 101k, False: 124k]
  ------------------
 3415|       |        // Sort the trim data by GraphIndex. In what follows, we will treat this sorted vector as
 3416|       |        // a map from GraphIndex to TrimTxData via locate_fn, and its ordering will not change
 3417|       |        // anymore.
 3418|   282k|        std::ranges::sort(trim_data, [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
 3419|       |
 3420|       |        // Add the explicitly added dependencies to deps_by_child.
 3421|   282k|        deps_by_child.insert(deps_by_child.end(),
 3422|   282k|                             clusterset.m_deps_to_add.begin() + group_data.m_deps_offset,
 3423|   282k|                             clusterset.m_deps_to_add.begin() + group_data.m_deps_offset + group_data.m_deps_count);
 3424|       |
 3425|       |        // Sort deps_by_child by child transaction GraphIndex. The order will not be changed
 3426|       |        // anymore after this.
 3427|   282k|        std::ranges::sort(deps_by_child, [](auto& a, auto& b) noexcept { return a.second < b.second; });
 3428|       |        // Fill m_parents_count and m_parents_offset in trim_data, as well as m_deps_left, and
 3429|       |        // initially populate trim_heap. Because of the sort above, all dependencies involving the
 3430|       |        // same child are grouped together, so a single linear scan suffices.
 3431|   282k|        auto deps_it = deps_by_child.begin();
 3432|  3.81M|        for (auto trim_it = trim_data.begin(); trim_it != trim_data.end(); ++trim_it) {
  ------------------
  |  Branch (3432:48): [True: 3.53M, False: 282k]
  ------------------
 3433|  3.53M|            trim_it->m_parent_offset = deps_it - deps_by_child.begin();
 3434|  3.53M|            trim_it->m_deps_left = 0;
 3435|  8.80M|            while (deps_it != deps_by_child.end() && deps_it->second == trim_it->m_index) {
  ------------------
  |  Branch (3435:20): [True: 8.45M, False: 345k]
  |  Branch (3435:20): [True: 5.26M, False: 3.53M]
  |  Branch (3435:54): [True: 5.26M, False: 3.18M]
  ------------------
 3436|  5.26M|                ++trim_it->m_deps_left;
 3437|  5.26M|                ++deps_it;
 3438|  5.26M|            }
 3439|  3.53M|            trim_it->m_parent_count = trim_it->m_deps_left;
 3440|       |            // If this transaction has no unmet dependencies, and is not oversized, add it to the
 3441|       |            // heap (just append for now, the heapification happens below).
 3442|  3.53M|            if (trim_it->m_deps_left == 0 && trim_it->m_tx_size <= m_max_cluster_size) {
  ------------------
  |  Branch (3442:17): [True: 688k, False: 2.84M]
  |  Branch (3442:46): [True: 561k, False: 126k]
  ------------------
 3443|   561k|                trim_heap.push_back(trim_it);
 3444|   561k|            }
 3445|  3.53M|        }
 3446|   282k|        Assume(deps_it == deps_by_child.end());
  ------------------
  |  |  128|   282k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3447|       |
 3448|       |        // Construct deps_by_parent, sorted by parent transaction GraphIndex. The order will not be
 3449|       |        // changed anymore after this.
 3450|   282k|        deps_by_parent = deps_by_child;
 3451|   282k|        std::ranges::sort(deps_by_parent, [](auto& a, auto& b) noexcept { return a.first < b.first; });
 3452|       |        // Fill m_children_offset and m_children_count in trim_data. Because of the sort above, all
 3453|       |        // dependencies involving the same parent are grouped together, so a single linear scan
 3454|       |        // suffices.
 3455|   282k|        deps_it = deps_by_parent.begin();
 3456|  3.53M|        for (auto& trim_entry : trim_data) {
  ------------------
  |  Branch (3456:31): [True: 3.53M, False: 282k]
  ------------------
 3457|  3.53M|            trim_entry.m_children_count = 0;
 3458|  3.53M|            trim_entry.m_children_offset = deps_it - deps_by_parent.begin();
 3459|  8.80M|            while (deps_it != deps_by_parent.end() && deps_it->first == trim_entry.m_index) {
  ------------------
  |  Branch (3459:20): [True: 8.45M, False: 340k]
  |  Branch (3459:20): [True: 5.26M, False: 3.53M]
  |  Branch (3459:55): [True: 5.26M, False: 3.19M]
  ------------------
 3460|  5.26M|                ++trim_entry.m_children_count;
 3461|  5.26M|                ++deps_it;
 3462|  5.26M|            }
 3463|  3.53M|        }
 3464|   282k|        Assume(deps_it == deps_by_parent.end());
  ------------------
  |  |  128|   282k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3465|       |
 3466|       |        // Build a heap of all transactions with 0 unmet dependencies.
 3467|   282k|        std::make_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
 3468|       |
 3469|       |        // Iterate over to-be-included transactions, and convert them to included transactions, or
 3470|       |        // skip them if adding them would violate resource limits of the would-be cluster.
 3471|       |        //
 3472|       |        // It is possible that the heap empties without ever hitting either cluster limit, in case
 3473|       |        // the implied graph (to be added dependencies plus implicit dependency between each
 3474|       |        // original transaction and its predecessor in the linearization it came from) contains
 3475|       |        // cycles. Such cycles will be removed entirely, because each of the transactions in the
 3476|       |        // cycle permanently have unmet dependencies. However, this cannot occur in real scenarios
 3477|       |        // where Trim() is called to deal with reorganizations that would violate cluster limits,
 3478|       |        // as all added dependencies are in the same direction (from old mempool transactions to
 3479|       |        // new from-block transactions); cycles require dependencies in both directions to be
 3480|       |        // added.
 3481|  3.32M|        while (!trim_heap.empty()) {
  ------------------
  |  Branch (3481:16): [True: 3.04M, False: 282k]
  ------------------
 3482|       |            // Move the best remaining transaction to the end of trim_heap.
 3483|  3.04M|            std::pop_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
 3484|       |            // Pop it, and find its TrimTxData.
 3485|  3.04M|            auto& entry = *trim_heap.back();
 3486|  3.04M|            trim_heap.pop_back();
 3487|       |
 3488|       |            // Initialize it as a singleton partition.
 3489|  3.04M|            entry.m_uf_parent = &entry;
 3490|  3.04M|            entry.m_uf_count = 1;
 3491|  3.04M|            entry.m_uf_size = entry.m_tx_size;
 3492|       |
 3493|       |            // Find the distinct transaction partitions this entry depends on.
 3494|  3.04M|            current_deps.clear();
 3495|  4.47M|            for (auto& [par, chl] : std::span{deps_by_child}.subspan(entry.m_parent_offset, entry.m_parent_count)) {
  ------------------
  |  Branch (3495:35): [True: 4.47M, False: 3.04M]
  ------------------
 3496|  4.47M|                Assume(chl == entry.m_index);
  ------------------
  |  |  128|  4.47M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3497|  4.47M|                current_deps.push_back(find_fn(&*locate_fn(par)));
 3498|  4.47M|            }
 3499|  3.04M|            std::ranges::sort(current_deps);
 3500|  3.04M|            current_deps.erase(std::ranges::unique(current_deps).begin(), current_deps.end());
 3501|       |
 3502|       |            // Compute resource counts.
 3503|  3.04M|            uint32_t new_count = 1;
 3504|  3.04M|            uint64_t new_size = entry.m_tx_size;
 3505|  3.04M|            for (TrimTxData* ptr : current_deps) {
  ------------------
  |  Branch (3505:34): [True: 2.85M, False: 3.04M]
  ------------------
 3506|  2.85M|                new_count += ptr->m_uf_count;
 3507|  2.85M|                new_size += ptr->m_uf_size;
 3508|  2.85M|            }
 3509|       |            // Skip the entry if this would violate any limit.
 3510|  3.04M|            if (new_count > m_max_cluster_count || new_size > m_max_cluster_size) continue;
  ------------------
  |  Branch (3510:17): [True: 233k, False: 2.80M]
  |  Branch (3510:52): [True: 4.21k, False: 2.80M]
  ------------------
 3511|       |
 3512|       |            // Union the partitions this transaction and all its dependencies are in together.
 3513|  2.80M|            auto rep = &entry;
 3514|  2.80M|            for (TrimTxData* ptr : current_deps) rep = union_fn(ptr, rep);
  ------------------
  |  Branch (3514:34): [True: 2.53M, False: 2.80M]
  ------------------
 3515|       |            // Mark the entry as included (so the loop below will not remove the transaction).
 3516|  2.80M|            entry.m_deps_left = uint32_t(-1);
 3517|       |            // Mark each to-be-added dependency involving this transaction as parent satisfied.
 3518|  4.74M|            for (auto& [par, chl] : std::span{deps_by_parent}.subspan(entry.m_children_offset, entry.m_children_count)) {
  ------------------
  |  Branch (3518:35): [True: 4.74M, False: 2.80M]
  ------------------
 3519|  4.74M|                Assume(par == entry.m_index);
  ------------------
  |  |  128|  4.74M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3520|  4.74M|                auto chl_it = locate_fn(chl);
 3521|       |                // Reduce the number of unmet dependencies of chl_it, and if that brings the number
 3522|       |                // to zero, add it to the heap of includable transactions.
 3523|  4.74M|                Assume(chl_it->m_deps_left > 0);
  ------------------
  |  |  128|  4.74M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3524|  4.74M|                if (--chl_it->m_deps_left == 0) {
  ------------------
  |  Branch (3524:21): [True: 2.47M, False: 2.26M]
  ------------------
 3525|  2.47M|                    trim_heap.push_back(chl_it);
 3526|  2.47M|                    std::push_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
 3527|  2.47M|                }
 3528|  4.74M|            }
 3529|  2.80M|        }
 3530|       |
 3531|       |        // Remove all the transactions that were not processed above. Because nothing gets
 3532|       |        // processed until/unless all its dependencies are met, this automatically guarantees
 3533|       |        // that if a transaction is removed, all its descendants, or would-be descendants, are
 3534|       |        // removed as well.
 3535|  3.53M|        for (const auto& trim_entry : trim_data) {
  ------------------
  |  Branch (3535:37): [True: 3.53M, False: 282k]
  ------------------
 3536|  3.53M|            if (trim_entry.m_deps_left != uint32_t(-1)) {
  ------------------
  |  Branch (3536:17): [True: 726k, False: 2.80M]
  ------------------
 3537|   726k|                ret.push_back(m_entries[trim_entry.m_index].m_ref);
 3538|   726k|                clusterset.m_to_remove.push_back(trim_entry.m_index);
 3539|   726k|            }
 3540|  3.53M|        }
 3541|   282k|    }
 3542|   114k|    clusterset.m_group_data.reset();
 3543|   114k|    clusterset.m_oversized = false;
 3544|   114k|    Assume(!ret.empty());
  ------------------
  |  |  128|   114k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3545|   114k|    return ret;
 3546|   114k|}
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_1clINS_10TrimTxDataES3_EEDaRT_RT0_:
 3418|   196M|        std::ranges::sort(trim_data, [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_2clINSt3__14pairIjjEES5_EEDaRT_RT0_:
 3427|   352M|        std::ranges::sort(deps_by_child, [](auto& a, auto& b) noexcept { return a.second < b.second; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_3clINSt3__14pairIjjEES5_EEDaRT_RT0_:
 3451|   361M|        std::ranges::sort(deps_by_parent, [](auto& a, auto& b) noexcept { return a.first < b.first; });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_4clINSt3__111__wrap_iterIPNS_10TrimTxDataEEES7_EEDaT_T0_:
 3349|  8.87M|    static constexpr auto cmp_fn = [](auto a, auto b) noexcept {
 3350|       |        // Sort by increasing chunk feerate, and then by decreasing size.
 3351|       |        // We do not need to sort by cluster or within clusters, because due to the implicit
 3352|       |        // dependency between consecutive linearization elements, no two transactions from the
 3353|       |        // same Cluster will ever simultaneously be in the heap.
 3354|  8.87M|        return ByRatioNegSize{a->m_chunk_feerate} < ByRatioNegSize{b->m_chunk_feerate};
 3355|  8.87M|    };
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_5clEPNS_10TrimTxDataE:
 3358|  9.54M|    static constexpr auto find_fn = [](TrimTxData* arg) noexcept {
 3359|  13.0M|        while (arg != arg->m_uf_parent) {
  ------------------
  |  Branch (3359:16): [True: 3.50M, False: 9.54M]
  ------------------
 3360|       |            // Replace pointer to parent with pointer to grandparent (path splitting).
 3361|       |            // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
 3362|  3.50M|            auto par = arg->m_uf_parent;
 3363|  3.50M|            arg->m_uf_parent = par->m_uf_parent;
 3364|  3.50M|            arg = par;
 3365|  3.50M|        }
 3366|  9.54M|        return arg;
 3367|  9.54M|    };
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_0clEj:
 3390|  9.22M|    auto locate_fn = [&](GraphIndex index) noexcept {
 3391|  9.22M|        auto it = std::lower_bound(trim_data.begin(), trim_data.end(), index, [](TrimTxData& elem, GraphIndex idx) noexcept {
 3392|  9.22M|            return elem.m_index < idx;
 3393|  9.22M|        });
 3394|  9.22M|        Assume(it != trim_data.end() && it->m_index == index);
  ------------------
  |  |  128|  18.4M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 9.22M, False: 0]
  |  |  |  Branch (128:51): [True: 9.22M, False: 0]
  |  |  ------------------
  ------------------
 3395|  9.22M|        return it;
 3396|  9.22M|    };
txgraph.cpp:_ZZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_0clEjENKUlRNS_10TrimTxDataEjE_clES3_j:
 3391|  50.1M|        auto it = std::lower_bound(trim_data.begin(), trim_data.end(), index, [](TrimTxData& elem, GraphIndex idx) noexcept {
 3392|  50.1M|            return elem.m_index < idx;
 3393|  50.1M|        });
txgraph.cpp:_ZZN12_GLOBAL__N_111TxGraphImpl4TrimEvENK3$_6clEPNS_10TrimTxDataES3_:
 3371|  2.53M|    static constexpr auto union_fn = [](TrimTxData* arg1, TrimTxData* arg2) noexcept {
 3372|       |        // Replace arg1 and arg2 by their representatives.
 3373|  2.53M|        auto rep1 = find_fn(arg1);
 3374|  2.53M|        auto rep2 = find_fn(arg2);
 3375|       |        // Bail out if both representatives are the same, because that means arg1 and arg2 are in
 3376|       |        // the same partition already.
 3377|  2.53M|        if (rep1 == rep2) return rep1;
  ------------------
  |  Branch (3377:13): [True: 0, False: 2.53M]
  ------------------
 3378|       |        // Pick the lower-count root to become a child of the higher-count one.
 3379|       |        // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_size.
 3380|  2.53M|        if (rep1->m_uf_count < rep2->m_uf_count) std::swap(rep1, rep2);
  ------------------
  |  Branch (3380:13): [True: 188k, False: 2.34M]
  ------------------
 3381|  2.53M|        rep2->m_uf_parent = rep1;
 3382|       |        // Add the statistics of arg2 (which is no longer a representative) to those of arg1 (which
 3383|       |        // is now the representative for both).
 3384|  2.53M|        rep1->m_uf_size += rep2->m_uf_size;
 3385|  2.53M|        rep1->m_uf_count += rep2->m_uf_count;
 3386|  2.53M|        return rep1;
 3387|  2.53M|    };
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl15GetBlockBuilderEv:
 3267|  19.5k|{
 3268|  19.5k|    return std::make_unique<BlockBuilderImpl>(*this);
 3269|  19.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImplC2ERNS_11TxGraphImplE:
 3220|  19.5k|BlockBuilderImpl::BlockBuilderImpl(TxGraphImpl& graph) noexcept : m_graph(&graph)
 3221|  19.5k|{
 3222|       |    // Make sure all clusters in main are up to date, and acceptable.
 3223|  19.5k|    m_graph->MakeAllAcceptable(0);
 3224|       |    // There cannot remain any inapplicable dependencies (only possible if main is oversized).
 3225|  19.5k|    Assume(m_graph->m_main_clusterset.m_deps_to_add.empty());
  ------------------
  |  |  128|  19.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3226|       |    // Remember that this object is observing the graph's index, so that we can detect concurrent
 3227|       |    // modifications.
 3228|  19.5k|    ++m_graph->m_main_chunkindex_observers;
 3229|       |    // Find the first chunk.
 3230|  19.5k|    m_cur_iter = m_graph->m_main_chunkindex.begin();
 3231|  19.5k|    m_cur_cluster = nullptr;
 3232|  19.5k|    if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
  ------------------
  |  Branch (3232:9): [True: 17.4k, False: 2.11k]
  ------------------
 3233|       |        // Find the cluster pointed to by m_cur_iter.
 3234|  17.4k|        const auto& chunk_data = *m_cur_iter;
 3235|  17.4k|        const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
 3236|  17.4k|        m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
 3237|  17.4k|    }
 3238|  19.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImplD2Ev:
 3241|  19.5k|{
 3242|  19.5k|    Assume(m_graph->m_main_chunkindex_observers > 0);
  ------------------
  |  |  128|  19.5k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3243|       |    // Permit modifications to the main graph again after destroying the BlockBuilderImpl.
 3244|  19.5k|    --m_graph->m_main_chunkindex_observers;
 3245|  19.5k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImpl15GetCurrentChunkEv:
 3192|   416k|{
 3193|   416k|    std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> ret;
 3194|       |    // Populate the return value if we are not done.
 3195|   416k|    if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
  ------------------
  |  Branch (3195:9): [True: 398k, False: 17.2k]
  ------------------
 3196|   398k|        ret.emplace();
 3197|   398k|        const auto& chunk_data = *m_cur_iter;
 3198|   398k|        const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
 3199|   398k|        if (chunk_data.m_chunk_count == LinearizationIndex(-1)) {
  ------------------
  |  Branch (3199:13): [True: 279k, False: 119k]
  ------------------
 3200|       |            // Special case in case just a single transaction remains, avoiding the need to
 3201|       |            // dispatch to and dereference Cluster.
 3202|   279k|            ret->first.resize(1);
 3203|   279k|            Assume(chunk_end_entry.m_ref != nullptr);
  ------------------
  |  |  128|   279k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3204|   279k|            ret->first[0] = chunk_end_entry.m_ref;
 3205|   279k|            m_known_end_of_cluster = true;
 3206|   279k|        } else {
 3207|   119k|            Assume(m_cur_cluster);
  ------------------
  |  |  128|   119k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3208|   119k|            ret->first.resize(chunk_data.m_chunk_count);
 3209|   119k|            auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
 3210|   119k|            m_known_end_of_cluster = m_cur_cluster->GetClusterRefs(*m_graph, ret->first, start_pos);
 3211|       |            // If the chunk size was 1 and at end of cluster, then the special case above should
 3212|       |            // have been used.
 3213|   119k|            Assume(!m_known_end_of_cluster || chunk_data.m_chunk_count > 1);
  ------------------
  |  |  128|   123k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 116k, False: 3.76k]
  |  |  |  Branch (128:51): [True: 3.76k, False: 0]
  |  |  ------------------
  ------------------
 3214|   119k|        }
 3215|   398k|        ret->second = chunk_end_entry.m_main_chunk_feerate;
 3216|   398k|    }
 3217|   416k|    return ret;
 3218|   416k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImpl7IncludeEv:
 3248|   394k|{
 3249|       |    // The actual inclusion of the chunk is done by the calling code. All we have to do is switch
 3250|       |    // to the next chunk.
 3251|   394k|    Next();
 3252|   394k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImpl4NextEv:
 3173|   399k|{
 3174|       |    // Don't do anything if we're already done.
 3175|   399k|    if (m_cur_iter == m_graph->m_main_chunkindex.end()) return;
  ------------------
  |  Branch (3175:9): [True: 1.60k, False: 398k]
  ------------------
 3176|   401k|    while (true) {
  ------------------
  |  Branch (3176:12): [True: 401k, Folded]
  ------------------
 3177|       |        // Advance the pointer, and stop if we reach the end.
 3178|   401k|        ++m_cur_iter;
 3179|   401k|        m_cur_cluster = nullptr;
 3180|   401k|        if (m_cur_iter == m_graph->m_main_chunkindex.end()) break;
  ------------------
  |  Branch (3180:13): [True: 14.3k, False: 387k]
  ------------------
 3181|       |        // Find the cluster pointed to by m_cur_iter.
 3182|   387k|        const auto& chunk_data = *m_cur_iter;
 3183|   387k|        const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
 3184|   387k|        m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
 3185|   387k|        m_known_end_of_cluster = false;
 3186|       |        // If we previously skipped a chunk from this cluster we cannot include more from it.
 3187|   387k|        if (!m_excluded_clusters.contains(m_cur_cluster->m_sequence)) break;
  ------------------
  |  Branch (3187:13): [True: 384k, False: 3.46k]
  ------------------
 3188|   387k|    }
 3189|   398k|}
txgraph.cpp:_ZN12_GLOBAL__N_116BlockBuilderImpl4SkipEv:
 3255|  5.87k|{
 3256|       |    // When skipping a chunk we need to not include anything more of the cluster, as that could make
 3257|       |    // the result topologically invalid. However, don't do this if the chunk is known to be the last
 3258|       |    // chunk of the cluster. This may significantly reduce the size of m_excluded_clusters,
 3259|       |    // especially when many singleton clusters are ignored.
 3260|  5.87k|    if (m_cur_cluster != nullptr && !m_known_end_of_cluster) {
  ------------------
  |  Branch (3260:9): [True: 4.73k, False: 1.13k]
  |  Branch (3260:37): [True: 2.32k, False: 2.40k]
  ------------------
 3261|  2.32k|        m_excluded_clusters.insert(m_cur_cluster->m_sequence);
 3262|  2.32k|    }
 3263|  5.87k|    Next();
 3264|  5.87k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl17GetWorstMainChunkEv:
 3272|  34.3k|{
 3273|  34.3k|    std::pair<std::vector<Ref*>, FeePerWeight> ret;
 3274|       |    // Make sure all clusters in main are up to date, and acceptable.
 3275|  34.3k|    MakeAllAcceptable(0);
 3276|  34.3k|    Assume(m_main_clusterset.m_deps_to_add.empty());
  ------------------
  |  |  128|  34.3k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3277|       |    // If the graph is not empty, populate ret.
 3278|  34.3k|    if (!m_main_chunkindex.empty()) {
  ------------------
  |  Branch (3278:9): [True: 31.5k, False: 2.83k]
  ------------------
 3279|  31.5k|        const auto& chunk_data = *m_main_chunkindex.rbegin();
 3280|  31.5k|        const auto& chunk_end_entry = m_entries[chunk_data.m_graph_index];
 3281|  31.5k|        Cluster* cluster = chunk_end_entry.m_locator[0].cluster;
 3282|  31.5k|        if (chunk_data.m_chunk_count == LinearizationIndex(-1) || chunk_data.m_chunk_count == 1)  {
  ------------------
  |  Branch (3282:13): [True: 29.1k, False: 2.37k]
  |  Branch (3282:67): [True: 0, False: 2.37k]
  ------------------
 3283|       |            // Special case for singletons.
 3284|  29.1k|            ret.first.resize(1);
 3285|  29.1k|            Assume(chunk_end_entry.m_ref != nullptr);
  ------------------
  |  |  128|  29.1k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
 3286|  29.1k|            ret.first[0] = chunk_end_entry.m_ref;
 3287|  29.1k|        } else {
 3288|  2.37k|            ret.first.resize(chunk_data.m_chunk_count);
 3289|  2.37k|            auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
 3290|  2.37k|            cluster->GetClusterRefs(*this, ret.first, start_pos);
 3291|  2.37k|            std::reverse(ret.first.begin(), ret.first.end());
 3292|  2.37k|        }
 3293|  31.5k|        ret.second = chunk_end_entry.m_main_chunk_feerate;
 3294|  31.5k|    }
 3295|  34.3k|    return ret;
 3296|  34.3k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl18GetMainMemoryUsageEv:
 3549|  33.8k|{
 3550|       |    // Make sure splits/merges are applied, as memory usage may not be representative otherwise.
 3551|  33.8k|    SplitAll(/*up_to_level=*/0);
 3552|  33.8k|    ApplyDependencies(/*level=*/0);
 3553|       |    // Compute memory usage
 3554|  33.8k|    size_t usage = /* From clusters */
 3555|  33.8k|                   m_main_clusterset.m_cluster_usage +
 3556|       |                   /* From Entry objects. */
 3557|  33.8k|                   sizeof(Entry) * m_main_clusterset.m_txcount +
 3558|       |                   /* From the chunk index. */
 3559|  33.8k|                   memusage::DynamicUsage(m_main_chunkindex);
 3560|  33.8k|    return usage;
 3561|  33.8k|}
txgraph.cpp:_ZNK12_GLOBAL__N_111TxGraphImpl11SanityCheckEv:
 2933|  36.0k|{
 2934|       |    /** Which GraphIndexes ought to occur in m_unlinked, based on m_entries. */
 2935|  36.0k|    std::set<GraphIndex> expected_unlinked;
 2936|       |    /** Which Clusters ought to occur in ClusterSet::m_clusters, based on m_entries. */
 2937|  36.0k|    std::set<const Cluster*> expected_clusters[MAX_LEVELS];
 2938|       |    /** Which GraphIndexes ought to occur in ClusterSet::m_removed, based on m_entries. */
 2939|  36.0k|    std::set<GraphIndex> expected_removed[MAX_LEVELS];
 2940|       |    /** Which Cluster::m_sequence values have been encountered. */
 2941|  36.0k|    std::set<uint64_t> sequences;
 2942|       |    /** Which Clusters are live in ClusterSet::m_clusters up to the level being checked. */
 2943|  36.0k|    std::set<const Cluster*> live_clusters;
 2944|       |    /** Which GraphIndexes ought to occur in m_main_chunkindex, based on m_entries. */
 2945|  36.0k|    std::set<GraphIndex> expected_chunkindex;
 2946|       |    /** Whether compaction is possible in the current state. */
 2947|  36.0k|    bool compact_possible{true};
 2948|       |
 2949|       |    // Go over all Entry objects in m_entries.
 2950|  1.48M|    for (GraphIndex idx = 0; idx < m_entries.size(); ++idx) {
  ------------------
  |  Branch (2950:30): [True: 1.44M, False: 36.0k]
  ------------------
 2951|  1.44M|        const auto& entry = m_entries[idx];
 2952|  1.44M|        if (entry.m_ref == nullptr) {
  ------------------
  |  Branch (2952:13): [True: 38.7k, False: 1.40M]
  ------------------
 2953|       |            // Unlinked Entry must have indexes appear in m_unlinked.
 2954|  38.7k|            expected_unlinked.insert(idx);
 2955|  1.40M|        } else {
 2956|       |            // Every non-unlinked Entry must have a Ref that points back to it.
 2957|  1.40M|            assert(GetRefGraph(*entry.m_ref) == this);
  ------------------
  |  Branch (2957:13): [True: 1.40M, False: 0]
  ------------------
 2958|  1.40M|            assert(GetRefIndex(*entry.m_ref) == idx);
  ------------------
  |  Branch (2958:13): [True: 1.40M, False: 0]
  ------------------
 2959|  1.40M|        }
 2960|  1.44M|        if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
  ------------------
  |  Branch (2960:13): [True: 887k, False: 557k]
  ------------------
 2961|       |            // Remember which entries we see a chunkindex entry for.
 2962|   887k|            assert(entry.m_locator[0].IsPresent());
  ------------------
  |  Branch (2962:13): [True: 887k, False: 0]
  ------------------
 2963|   887k|            expected_chunkindex.insert(idx);
 2964|   887k|        }
 2965|       |        // Verify the Entry m_locators.
 2966|  1.44M|        bool was_present{false}, was_removed{false};
 2967|  4.33M|        for (int level = 0; level < MAX_LEVELS; ++level) {
  ------------------
  |  Branch (2967:29): [True: 2.89M, False: 1.44M]
  ------------------
 2968|  2.89M|            const auto& locator = entry.m_locator[level];
 2969|       |            // Every Locator must be in exactly one of these 3 states.
 2970|  2.89M|            assert(locator.IsMissing() + locator.IsRemoved() + locator.IsPresent() == 1);
  ------------------
  |  Branch (2970:13): [True: 2.89M, False: 0]
  ------------------
 2971|  2.89M|            if (locator.IsPresent()) {
  ------------------
  |  Branch (2971:17): [True: 1.37M, False: 1.51M]
  ------------------
 2972|       |                // Once removed, a transaction cannot be revived.
 2973|  1.37M|                assert(!was_removed);
  ------------------
  |  Branch (2973:17): [True: 1.37M, False: 0]
  ------------------
 2974|       |                // Verify that the Cluster agrees with where the Locator claims the transaction is.
 2975|  1.37M|                assert(locator.cluster->GetClusterEntry(locator.index) == idx);
  ------------------
  |  Branch (2975:17): [True: 1.37M, False: 0]
  ------------------
 2976|       |                // Remember that we expect said Cluster to appear in the ClusterSet::m_clusters.
 2977|  1.37M|                expected_clusters[level].insert(locator.cluster);
 2978|  1.37M|                was_present = true;
 2979|  1.51M|            } else if (locator.IsRemoved()) {
  ------------------
  |  Branch (2979:24): [True: 75.5k, False: 1.44M]
  ------------------
 2980|       |                // Level 0 (main) cannot have IsRemoved locators (IsMissing there means non-existing).
 2981|  75.5k|                assert(level > 0);
  ------------------
  |  Branch (2981:17): [True: 75.5k, False: 0]
  ------------------
 2982|       |                // A Locator can only be IsRemoved if it was IsPresent before, and only once.
 2983|  75.5k|                assert(was_present && !was_removed);
  ------------------
  |  Branch (2983:17): [True: 75.5k, False: 0]
  |  Branch (2983:17): [True: 75.5k, False: 0]
  |  Branch (2983:17): [True: 75.5k, False: 0]
  ------------------
 2984|       |                // Remember that we expect this GraphIndex to occur in the ClusterSet::m_removed.
 2985|  75.5k|                expected_removed[level].insert(idx);
 2986|  75.5k|                was_removed = true;
 2987|  75.5k|            }
 2988|  2.89M|        }
 2989|  1.44M|    }
 2990|       |
 2991|       |    // For all levels (0 = main, 1 = staged)...
 2992|  86.8k|    for (int level = 0; level <= GetTopLevel(); ++level) {
  ------------------
  |  Branch (2992:25): [True: 50.7k, False: 36.0k]
  ------------------
 2993|  50.7k|        assert(level < MAX_LEVELS);
  ------------------
  |  Branch (2993:9): [True: 50.7k, False: 0]
  ------------------
 2994|  50.7k|        auto& clusterset = GetClusterSet(level);
 2995|  50.7k|        std::set<const Cluster*> actual_clusters;
 2996|  50.7k|        size_t recomputed_cluster_usage{0};
 2997|       |
 2998|       |        // For all quality levels...
 2999|   406k|        for (int qual = 0; qual < int(QualityLevel::NONE); ++qual) {
  ------------------
  |  Branch (2999:28): [True: 355k, False: 50.7k]
  ------------------
 3000|   355k|            QualityLevel quality{qual};
 3001|   355k|            const auto& quality_clusters = clusterset.m_clusters[qual];
 3002|       |            // ... for all clusters in them ...
 3003|  1.19M|            for (ClusterSetIndex setindex = 0; setindex < quality_clusters.size(); ++setindex) {
  ------------------
  |  Branch (3003:48): [True: 835k, False: 355k]
  ------------------
 3004|   835k|                const auto& cluster = *quality_clusters[setindex];
 3005|   835k|                live_clusters.insert(&cluster);
 3006|       |                // The number of transactions in a Cluster cannot exceed m_max_cluster_count.
 3007|   835k|                assert(cluster.GetTxCount() <= m_max_cluster_count);
  ------------------
  |  Branch (3007:17): [True: 835k, False: 0]
  ------------------
 3008|       |                // The level must match the Cluster's own idea of what level it is in (but GetLevel
 3009|       |                // can only be called for non-empty Clusters).
 3010|   835k|                assert(cluster.GetTxCount() == 0 || level == cluster.GetLevel(*this));
  ------------------
  |  Branch (3010:17): [True: 14.6k, False: 821k]
  |  Branch (3010:17): [True: 821k, False: 0]
  |  Branch (3010:17): [True: 835k, False: 0]
  ------------------
 3011|       |                // The sum of their sizes cannot exceed m_max_cluster_size, unless it is an
 3012|       |                // individually oversized transaction singleton. Note that groups of to-be-merged
 3013|       |                // clusters which would exceed this limit are marked oversized, which means they
 3014|       |                // are never applied.
 3015|   835k|                assert(cluster.IsOversized() || cluster.GetTotalTxSize() <= m_max_cluster_size);
  ------------------
  |  Branch (3015:17): [True: 50.2k, False: 785k]
  |  Branch (3015:17): [True: 785k, False: 0]
  |  Branch (3015:17): [True: 835k, False: 0]
  ------------------
 3016|       |                // OVERSIZED clusters are singletons.
 3017|   835k|                assert(!cluster.IsOversized() || cluster.GetTxCount() == 1);
  ------------------
  |  Branch (3017:17): [True: 785k, False: 50.2k]
  |  Branch (3017:17): [True: 50.2k, False: 0]
  |  Branch (3017:17): [True: 835k, False: 0]
  ------------------
 3018|       |                // Transaction counts cannot exceed the Cluster implementation's maximum
 3019|       |                // supported transactions count.
 3020|   835k|                assert(cluster.GetTxCount() <= cluster.GetMaxTxCount());
  ------------------
  |  Branch (3020:17): [True: 835k, False: 0]
  ------------------
 3021|       |                // Unless a Split is yet to be applied, the number of transactions must not be
 3022|       |                // below the Cluster implementation's intended transaction count.
 3023|   835k|                if (!cluster.NeedsSplitting()) {
  ------------------
  |  Branch (3023:21): [True: 820k, False: 15.1k]
  ------------------
 3024|   820k|                    assert(cluster.GetTxCount() >= cluster.GetMinIntendedTxCount());
  ------------------
  |  Branch (3024:21): [True: 820k, False: 0]
  ------------------
 3025|   820k|                }
 3026|       |
 3027|       |                // Check the sequence number.
 3028|   835k|                assert(cluster.m_sequence < m_next_sequence_counter);
  ------------------
  |  Branch (3028:17): [True: 835k, False: 0]
  ------------------
 3029|   835k|                assert(!sequences.contains(cluster.m_sequence));
  ------------------
  |  Branch (3029:17): [True: 835k, False: 0]
  ------------------
 3030|   835k|                sequences.insert(cluster.m_sequence);
 3031|       |                // Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't
 3032|       |                // expected to be referenced by the Entry vector).
 3033|   835k|                if (cluster.GetTxCount() != 0) {
  ------------------
  |  Branch (3033:21): [True: 821k, False: 14.6k]
  ------------------
 3034|   821k|                    actual_clusters.insert(&cluster);
 3035|   821k|                }
 3036|       |                // Sanity check the cluster, according to the Cluster's internal rules.
 3037|   835k|                cluster.SanityCheck(*this, level);
 3038|       |                // Check that the cluster's quality and setindex matches its position in the quality list.
 3039|   835k|                assert(cluster.m_quality == quality);
  ------------------
  |  Branch (3039:17): [True: 835k, False: 0]
  ------------------
 3040|   835k|                assert(cluster.m_setindex == setindex);
  ------------------
  |  Branch (3040:17): [True: 835k, False: 0]
  ------------------
 3041|       |                // Count memory usage.
 3042|   835k|                recomputed_cluster_usage += cluster.TotalMemoryUsage();
 3043|   835k|            }
 3044|   355k|        }
 3045|       |
 3046|       |        // Verify memory usage.
 3047|  50.7k|        assert(clusterset.m_cluster_usage == recomputed_cluster_usage);
  ------------------
  |  Branch (3047:9): [True: 50.7k, False: 0]
  ------------------
 3048|       |
 3049|       |        // Verify that all to-be-removed transactions have valid identifiers.
 3050|  52.8k|        for (GraphIndex idx : clusterset.m_to_remove) {
  ------------------
  |  Branch (3050:29): [True: 52.8k, False: 50.7k]
  ------------------
 3051|  52.8k|            assert(idx < m_entries.size());
  ------------------
  |  Branch (3051:13): [True: 52.8k, False: 0]
  ------------------
 3052|       |            // We cannot assert that all m_to_remove transactions are still present: ~Ref on a
 3053|       |            // (P,M) transaction (present in main, inherited in staging) will cause an m_to_remove
 3054|       |            // addition in both main and staging, but a subsequence ApplyRemovals in main will
 3055|       |            // cause it to disappear from staging too, leaving the m_to_remove in place.
 3056|  52.8k|        }
 3057|       |
 3058|       |        // Verify that all to-be-added dependencies have valid identifiers.
 3059|   365k|        for (auto [par_idx, chl_idx] : clusterset.m_deps_to_add) {
  ------------------
  |  Branch (3059:38): [True: 365k, False: 50.7k]
  ------------------
 3060|   365k|            assert(par_idx != chl_idx);
  ------------------
  |  Branch (3060:13): [True: 365k, False: 0]
  ------------------
 3061|   365k|            assert(par_idx < m_entries.size());
  ------------------
  |  Branch (3061:13): [True: 365k, False: 0]
  ------------------
 3062|   365k|            assert(chl_idx < m_entries.size());
  ------------------
  |  Branch (3062:13): [True: 365k, False: 0]
  ------------------
 3063|   365k|        }
 3064|       |
 3065|       |        // Verify that the actually encountered clusters match the ones occurring in Entry vector.
 3066|  50.7k|        assert(actual_clusters == expected_clusters[level]);
  ------------------
  |  Branch (3066:9): [True: 50.7k, False: 0]
  ------------------
 3067|       |
 3068|       |        // Validate m_group_data cluster pointers refer to live clusters at this
 3069|       |        // level or below (staging group data may reference not-yet-pulled-in
 3070|       |        // main clusters).
 3071|  50.7k|        if (clusterset.m_group_data.has_value()) {
  ------------------
  |  Branch (3071:13): [True: 40.0k, False: 10.7k]
  ------------------
 3072|  51.6k|            for (const Cluster* cl : clusterset.m_group_data->m_group_clusters) {
  ------------------
  |  Branch (3072:36): [True: 51.6k, False: 40.0k]
  ------------------
 3073|  51.6k|                assert(live_clusters.contains(cl));
  ------------------
  |  Branch (3073:17): [True: 51.6k, False: 0]
  ------------------
 3074|  51.6k|                assert(cl->GetTxCount() > 0);
  ------------------
  |  Branch (3074:17): [True: 51.6k, False: 0]
  ------------------
 3075|  51.6k|                assert(FindCluster(cl->GetClusterEntry(0), level) == cl);
  ------------------
  |  Branch (3075:17): [True: 51.6k, False: 0]
  ------------------
 3076|  51.6k|            }
 3077|  40.0k|        }
 3078|       |        // Verify that the contents of m_removed matches what was expected based on the Entry vector.
 3079|  50.7k|        std::set<GraphIndex> actual_removed(clusterset.m_removed.begin(), clusterset.m_removed.end());
 3080|  57.5k|        for (auto i : expected_unlinked) {
  ------------------
  |  Branch (3080:21): [True: 57.5k, False: 50.7k]
  ------------------
 3081|       |            // If a transaction exists in both main and staging, and is removed from staging (adding
 3082|       |            // it to m_removed there), and consequently destroyed (wiping the locator completely),
 3083|       |            // it can remain in m_removed despite not having an IsRemoved() locator. Exclude those
 3084|       |            // transactions from the comparison here.
 3085|  57.5k|            actual_removed.erase(i);
 3086|  57.5k|            expected_removed[level].erase(i);
 3087|  57.5k|        }
 3088|       |
 3089|  50.7k|        assert(actual_removed == expected_removed[level]);
  ------------------
  |  Branch (3089:9): [True: 50.7k, False: 0]
  ------------------
 3090|       |
 3091|       |        // If any GraphIndex entries remain in this ClusterSet, compact is not possible.
 3092|  50.7k|        if (!clusterset.m_deps_to_add.empty()) compact_possible = false;
  ------------------
  |  Branch (3092:13): [True: 8.97k, False: 41.8k]
  ------------------
 3093|  50.7k|        if (!clusterset.m_to_remove.empty()) compact_possible = false;
  ------------------
  |  Branch (3093:13): [True: 5.49k, False: 45.2k]
  ------------------
 3094|  50.7k|        if (!clusterset.m_removed.empty()) compact_possible = false;
  ------------------
  |  Branch (3094:13): [True: 7.41k, False: 43.3k]
  ------------------
 3095|       |
 3096|       |        // For non-top levels, m_oversized must be known (as it cannot change until the level
 3097|       |        // on top is gone).
 3098|  50.7k|        if (level < GetTopLevel()) assert(clusterset.m_oversized.has_value());
  ------------------
  |  Branch (3098:13): [True: 14.7k, False: 36.0k]
  |  Branch (3098:36): [True: 14.7k, False: 0]
  ------------------
 3099|  50.7k|    }
 3100|       |
 3101|       |    // Verify that the contents of m_unlinked matches what was expected based on the Entry vector.
 3102|  36.0k|    std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end());
 3103|  36.0k|    assert(actual_unlinked == expected_unlinked);
  ------------------
  |  Branch (3103:5): [True: 36.0k, False: 0]
  ------------------
 3104|       |
 3105|       |    // If compaction was possible, it should have been performed already, and m_unlinked must be
 3106|       |    // empty (to prevent memory leaks due to an ever-growing m_entries vector).
 3107|  36.0k|    if (compact_possible) {
  ------------------
  |  Branch (3107:9): [True: 22.0k, False: 13.9k]
  ------------------
 3108|  22.0k|        assert(actual_unlinked.empty());
  ------------------
  |  Branch (3108:9): [True: 22.0k, False: 0]
  ------------------
 3109|  22.0k|    }
 3110|       |
 3111|       |    // Finally, check the chunk index.
 3112|  36.0k|    std::set<GraphIndex> actual_chunkindex;
 3113|  36.0k|    FeeFrac last_chunk_feerate;
 3114|   887k|    for (const auto& chunk : m_main_chunkindex) {
  ------------------
  |  Branch (3114:28): [True: 887k, False: 36.0k]
  ------------------
 3115|   887k|        GraphIndex idx = chunk.m_graph_index;
 3116|   887k|        actual_chunkindex.insert(idx);
 3117|   887k|        auto chunk_feerate = m_entries[idx].m_main_chunk_feerate;
 3118|   887k|        if (!last_chunk_feerate.IsEmpty()) {
  ------------------
  |  Branch (3118:13): [True: 855k, False: 32.5k]
  ------------------
 3119|   855k|            assert(ByRatio{last_chunk_feerate} >= ByRatio{FeeFrac{chunk_feerate}});
  ------------------
  |  Branch (3119:13): [True: 855k, False: 0]
  ------------------
 3120|   855k|        }
 3121|   887k|        last_chunk_feerate = chunk_feerate;
 3122|   887k|    }
 3123|  36.0k|    assert(actual_chunkindex == expected_chunkindex);
  ------------------
  |  Branch (3123:5): [True: 36.0k, False: 0]
  ------------------
 3124|  36.0k|}
txgraph.cpp:_ZN12_GLOBAL__N_111TxGraphImpl9UnlinkRefEj:
  731|   187k|    {
  732|   187k|        auto& entry = m_entries[idx];
  733|   187k|        Assume(entry.m_ref != nullptr);
  ------------------
  |  |  128|   187k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  734|   187k|        Assume(m_main_chunkindex_observers == 0 || !entry.m_locator[0].IsPresent());
  ------------------
  |  |  128|   190k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 184k, False: 2.65k]
  |  |  |  Branch (128:51): [True: 2.65k, False: 0]
  |  |  ------------------
  ------------------
  735|       |        // Remove all chunk index entries for the affected cluster, to avoid any chunk indexes
  736|       |        // referencing unlinked/destroyed Refs.
  737|   187k|        if (entry.m_locator[0].IsPresent()) {
  ------------------
  |  Branch (737:13): [True: 136k, False: 50.8k]
  ------------------
  738|   136k|            entry.m_locator[0].cluster->RemoveChunkData(*this);
  739|   136k|        }
  740|   187k|        entry.m_ref = nullptr;
  741|       |        // Mark the transaction as to be removed in all levels where it explicitly or implicitly
  742|       |        // exists.
  743|   187k|        bool exists_anywhere{false};
  744|   187k|        bool exists{false};
  745|   388k|        for (int level = 0; level <= GetTopLevel(); ++level) {
  ------------------
  |  Branch (745:29): [True: 200k, False: 187k]
  ------------------
  746|   200k|            if (entry.m_locator[level].IsPresent()) {
  ------------------
  |  Branch (746:17): [True: 141k, False: 59.0k]
  ------------------
  747|   141k|                exists_anywhere = true;
  748|   141k|                exists = true;
  749|   141k|            } else if (entry.m_locator[level].IsRemoved()) {
  ------------------
  |  Branch (749:24): [True: 1.36k, False: 57.6k]
  ------------------
  750|  1.36k|                exists = false;
  751|  1.36k|            }
  752|   200k|            if (exists) {
  ------------------
  |  Branch (752:17): [True: 148k, False: 52.7k]
  ------------------
  753|   148k|                auto& clusterset = GetClusterSet(level);
  754|   148k|                clusterset.m_to_remove.push_back(idx);
  755|       |                // Force recomputation of grouping data.
  756|   148k|                clusterset.m_group_data = std::nullopt;
  757|       |                // Do not wipe the oversized state of main if staging exists. The reason for this
  758|       |                // is that the alternative would mean that cluster merges may need to be applied to
  759|       |                // a formerly-oversized main graph while staging exists (to satisfy chunk feerate
  760|       |                // queries into main, for example), and such merges could conflict with pulls of
  761|       |                // some of their constituents into staging.
  762|   148k|                if (level == GetTopLevel() && clusterset.m_oversized == true) {
  ------------------
  |  Branch (762:21): [True: 138k, False: 9.56k]
  |  Branch (762:21): [True: 4.72k, False: 143k]
  |  Branch (762:47): [True: 4.72k, False: 133k]
  ------------------
  763|  4.72k|                    clusterset.m_oversized = std::nullopt;
  764|  4.72k|                }
  765|   148k|            }
  766|   200k|        }
  767|   187k|        m_unlinked.push_back(idx);
  768|   187k|        if (!exists_anywhere) Compact();
  ------------------
  |  Branch (768:13): [True: 47.5k, False: 139k]
  ------------------
  769|   187k|    }

_ZN7TxGraphD2Ev:
   70|  20.6k|    virtual ~TxGraph() = default;
_ZN7TxGraph11GetRefGraphERNS_3RefE:
  226|  3.89M|    static TxGraph*& GetRefGraph(Ref& arg) noexcept { return arg.m_graph; }
_ZN7TxGraph11GetRefIndexERNS_3RefE:
  228|  2.33M|    static GraphIndex& GetRefIndex(Ref& arg) noexcept { return arg.m_index; }
_ZN7TxGraph11GetRefGraphERKNS_3RefE:
  227|   203M|    static TxGraph* GetRefGraph(const Ref& arg) noexcept { return arg.m_graph; }
_ZN7TxGraph11GetRefIndexERKNS_3RefE:
  229|   356M|    static GraphIndex GetRefIndex(const Ref& arg) noexcept { return arg.m_index; }
_ZN7TxGraph12BlockBuilderC2Ev:
  185|  19.5k|        BlockBuilder() noexcept = default;
_ZN7TxGraph12BlockBuilderD2Ev:
  188|  19.5k|        virtual ~BlockBuilder() = default;
_ZN7TxGraph3RefC2Ev:
  242|   908k|        Ref() noexcept = default;

_ZNK9base_blobILj256EE6GetHexEv:
   12|  18.0k|{
   13|  18.0k|    uint8_t m_data_rev[WIDTH];
   14|   594k|    for (int i = 0; i < WIDTH; ++i) {
  ------------------
  |  Branch (14:21): [True: 576k, False: 18.0k]
  ------------------
   15|   576k|        m_data_rev[i] = m_data[WIDTH - 1 - i];
   16|   576k|    }
   17|  18.0k|    return HexStr(m_data_rev);
   18|  18.0k|}

_ZN9base_blobILj256EE4dataEv:
   99|  12.1k|    constexpr unsigned char* data() { return m_data.data(); }
_ZN7uint256C2Ev:
  200|  12.1k|    constexpr uint256() = default;
_ZN9base_blobILj256EEC2Ev:
   37|  12.1k|    constexpr base_blob() : m_data() {}
_ZNK9base_blobILj256EE4dataEv:
   98|  30.1k|    constexpr const unsigned char* data() const { return m_data.data(); }
_ZN9base_blobILj256EE4sizeEv:
  107|  42.3k|    static constexpr unsigned int size() { return WIDTH; }

_ZN13bitset_detail14MultiIntBitSetImLj2EE9SingletonEj:
  356|  44.9M|    {
  357|  44.9M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  44.9M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  358|  44.9M|        MultiIntBitSet ret;
  359|  44.9M|        ret.m_val[pos / LIMB_BITS] = I{1U} << (pos % LIMB_BITS);
  360|  44.9M|        return ret;
  361|  44.9M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EEixEj:
  350|   201M|    {
  351|   201M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|   201M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  352|   201M|        return (m_val[pos / LIMB_BITS] >> (pos % LIMB_BITS)) & 1U;
  353|   201M|    }
_ZN13bitset_detail14MultiIntBitSetImLj2EEaNERKS1_:
  430|   153M|    {
  431|   460M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (431:30): [True: 306M, False: 153M]
  ------------------
  432|   306M|            m_val[i] &= a.m_val[i];
  433|   306M|        }
  434|   153M|        return *this;
  435|   153M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE5CountEv:
  381|  25.6M|    {
  382|  25.6M|        unsigned ret{0};
  383|  51.3M|        for (I v : m_val) ret += PopCount(v);
  ------------------
  |  Branch (383:18): [True: 51.3M, False: 25.6M]
  ------------------
  384|  25.6M|        return ret;
  385|  25.6M|    }
_ZN13bitset_detaileqERKNS_14MultiIntBitSetImLj2EEES3_:
  513|   121M|    friend constexpr bool operator==(const MultiIntBitSet& a, const MultiIntBitSet& b) noexcept = default;
_ZN13bitset_detail14MultiIntBitSetImLj2EEC2Ev:
  312|   160M|    constexpr MultiIntBitSet() noexcept : m_val{} {}
_ZN13bitset_detail14MultiIntBitSetImLj2EEoRERKS1_:
  422|   246M|    {
  423|   740M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (423:30): [True: 493M, False: 246M]
  ------------------
  424|   493M|            m_val[i] |= a.m_val[i];
  425|   493M|        }
  426|   246M|        return *this;
  427|   246M|    }
_ZN13bitset_detail14MultiIntBitSetImLj2EE5ResetEj:
  344|  14.5M|    {
  345|  14.5M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  14.5M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  346|  14.5M|        m_val[pos / LIMB_BITS] &= ~I(I{1U} << (pos % LIMB_BITS));
  347|  14.5M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE8OverlapsERKS1_:
  454|  7.21M|    {
  455|  21.0M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (455:30): [True: 14.1M, False: 6.90M]
  ------------------
  456|  14.1M|            if (m_val[i] & a.m_val[i]) return true;
  ------------------
  |  Branch (456:17): [True: 316k, False: 13.8M]
  ------------------
  457|  14.1M|        }
  458|  6.90M|        return false;
  459|  7.21M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE4NoneEv:
  388|   113M|    {
  389|   157M|        for (auto v : m_val) {
  ------------------
  |  Branch (389:21): [True: 157M, False: 34.8M]
  ------------------
  390|   157M|            if (v != 0) return false;
  ------------------
  |  Branch (390:17): [True: 78.3M, False: 79.5M]
  ------------------
  391|   157M|        }
  392|  34.8M|        return true;
  393|   113M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE5beginEv:
  397|  84.7M|    Iterator constexpr begin() const noexcept { return Iterator(m_val); }
_ZN13bitset_detail14MultiIntBitSetImLj2EE8IteratorC2ERKNSt3__15arrayImLm2EEE:
  259|  84.7M|        constexpr Iterator(const std::array<I, N>& ref) noexcept : m_ptr(&ref), m_idx(0)
  260|  84.7M|        {
  261|  96.5M|            do {
  262|  96.5M|                m_val = (*m_ptr)[m_idx];
  263|  96.5M|                if (m_val) {
  ------------------
  |  Branch (263:21): [True: 82.7M, False: 13.8M]
  ------------------
  264|  82.7M|                    m_pos = std::countr_zero(m_val) + m_idx * LIMB_BITS;
  265|  82.7M|                    break;
  266|  82.7M|                }
  267|  13.8M|                ++m_idx;
  268|  13.8M|            } while(m_idx < N);
  ------------------
  |  Branch (268:21): [True: 11.7M, False: 2.07M]
  ------------------
  269|  84.7M|        }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE3endEv:
  399|  84.8M|    IteratorEnd constexpr end() const noexcept { return IteratorEnd(); }
_ZN13bitset_detaileqERKNS_14MultiIntBitSetImLj2EE8IteratorERKNS1_11IteratorEndE:
  279|   265M|        {
  280|   265M|            return a.m_idx == N;
  281|   265M|        }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE8IteratordeEv:
  304|   189M|        {
  305|   189M|            Assume(m_idx < N);
  ------------------
  |  |  128|   189M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  306|   189M|            return m_pos;
  307|   189M|        }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE10IsSubsetOfERKS1_:
  506|  67.8M|    {
  507|   203M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (507:30): [True: 135M, False: 67.8M]
  ------------------
  508|   135M|            if (m_val[i] & ~a.m_val[i]) return false;
  ------------------
  |  Branch (508:17): [True: 0, False: 135M]
  ------------------
  509|   135M|        }
  510|  67.8M|        return true;
  511|  67.8M|    }
_ZN13bitset_detail14MultiIntBitSetImLj2EE8IteratorppEv:
  284|   181M|        {
  285|   181M|            Assume(m_idx < N);
  ------------------
  |  |  128|   181M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  286|   181M|            m_val &= m_val - I{1U};
  287|   181M|            if (m_val == 0) {
  ------------------
  |  Branch (287:17): [True: 79.5M, False: 101M]
  ------------------
  288|   140M|                while (true) {
  ------------------
  |  Branch (288:24): [True: 140M, Folded]
  ------------------
  289|   140M|                    ++m_idx;
  290|   140M|                    if (m_idx == N) break;
  ------------------
  |  Branch (290:25): [True: 74.4M, False: 65.6M]
  ------------------
  291|  65.6M|                    m_val = (*m_ptr)[m_idx];
  292|  65.6M|                    if (m_val) {
  ------------------
  |  Branch (292:25): [True: 5.16M, False: 60.5M]
  ------------------
  293|  5.16M|                        m_pos = std::countr_zero(m_val) + m_idx * LIMB_BITS;
  294|  5.16M|                        break;
  295|  5.16M|                    }
  296|  65.6M|                }
  297|   101M|            } else {
  298|   101M|                m_pos = std::countr_zero(m_val) + m_idx * LIMB_BITS;
  299|   101M|            }
  300|   181M|            return *this;
  301|   181M|        }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE3AnyEv:
  395|  90.6M|    bool constexpr Any() const noexcept { return !None(); }
_ZN13bitset_detail14MultiIntBitSetImLj2EE3SetEj:
  319|  46.5M|    {
  320|  46.5M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  46.5M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  321|  46.5M|        m_val[pos / LIMB_BITS] |= I{1U} << (pos % LIMB_BITS);
  322|  46.5M|    }
_ZNK13bitset_detail14MultiIntBitSetImLj2EE5FirstEv:
  402|  20.6M|    {
  403|  20.6M|        unsigned p = 0;
  404|  23.6M|        while (m_val[p] == 0) {
  ------------------
  |  Branch (404:16): [True: 3.04M, False: 20.6M]
  ------------------
  405|  3.04M|            ++p;
  406|  3.04M|            Assume(p < N);
  ------------------
  |  |  128|  3.04M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  407|  3.04M|        }
  408|  20.6M|        return std::countr_zero(m_val[p]) + p * LIMB_BITS;
  409|  20.6M|    }
_ZN13bitset_detailorERKNS_14MultiIntBitSetImLj2EEES3_:
  471|  1.78M|    {
  472|  1.78M|        MultiIntBitSet r;
  473|  5.35M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (473:30): [True: 3.56M, False: 1.78M]
  ------------------
  474|  3.56M|            r.m_val[i] = a.m_val[i] | b.m_val[i];
  475|  3.56M|        }
  476|  1.78M|        return r;
  477|  1.78M|    }
_ZN13bitset_detailmiERKNS_14MultiIntBitSetImLj2EEES3_:
  480|  79.3M|    {
  481|  79.3M|        MultiIntBitSet r;
  482|   238M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (482:30): [True: 158M, False: 79.3M]
  ------------------
  483|   158M|            r.m_val[i] = a.m_val[i] & ~b.m_val[i];
  484|   158M|        }
  485|  79.3M|        return r;
  486|  79.3M|    }
_ZN13bitset_detail14MultiIntBitSetImLj2EEmIERKS1_:
  438|  15.8M|    {
  439|  47.4M|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (439:30): [True: 31.6M, False: 15.8M]
  ------------------
  440|  31.6M|            m_val[i] &= ~a.m_val[i];
  441|  31.6M|        }
  442|  15.8M|        return *this;
  443|  15.8M|    }
_ZN13bitset_detail14MultiIntBitSetImLj2EE4FillEj:
  364|  2.67k|    {
  365|  2.67k|        Assume(count <= MAX_SIZE);
  ------------------
  |  |  128|  2.67k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  366|  2.67k|        MultiIntBitSet ret;
  367|  2.67k|        if (count) {
  ------------------
  |  Branch (367:13): [True: 2.57k, False: 95]
  ------------------
  368|  2.57k|            unsigned i = 0;
  369|  3.27k|            while (count > LIMB_BITS) {
  ------------------
  |  Branch (369:20): [True: 694, False: 2.57k]
  ------------------
  370|    694|                ret.m_val[i++] = I(~I{0});
  371|    694|                count -= LIMB_BITS;
  372|    694|            }
  373|  2.57k|            ret.m_val[i] = I(~I{0}) >> (LIMB_BITS - count);
  374|  2.57k|        }
  375|  2.67k|        return ret;
  376|  2.67k|    }
_ZN13bitset_detailanERKNS_14MultiIntBitSetImLj2EEES3_:
  462|   180k|    {
  463|   180k|        MultiIntBitSet r;
  464|   541k|        for (unsigned i = 0; i < N; ++i) {
  ------------------
  |  Branch (464:30): [True: 360k, False: 180k]
  ------------------
  465|   360k|            r.m_val[i] = a.m_val[i] & b.m_val[i];
  466|   360k|        }
  467|   180k|        return r;
  468|   180k|    }
_ZN13bitset_detaileqERKNS_9IntBitSetImEES3_:
  218|  1.45M|    friend constexpr bool operator==(const IntBitSet& a, const IntBitSet& b) noexcept = default;
_ZN13bitset_detail9IntBitSetImEC2Ev:
  120|  33.1M|    constexpr IntBitSet() noexcept : m_val{0} {}
_ZN13bitset_detail9IntBitSetImE4FillEj:
  145|   325k|    {
  146|   325k|        IntBitSet ret;
  147|   325k|        Assume(count <= MAX_SIZE);
  ------------------
  |  |  128|   325k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  148|   325k|        if (count) ret.m_val = I(~I{0}) >> (MAX_SIZE - count);
  ------------------
  |  Branch (148:13): [True: 325k, False: 0]
  ------------------
  149|   325k|        return ret;
  150|   325k|    }
_ZN13bitset_detailmiERKNS_9IntBitSetImEES3_:
  214|  10.1M|    friend constexpr IntBitSet operator-(const IntBitSet& a, const IntBitSet& b) noexcept { return I(a.m_val & ~b.m_val); }
_ZN13bitset_detail9IntBitSetImEC2Em:
   72|  36.2M|    IntBitSet(I val) noexcept : m_val{val} {}
_ZNK13bitset_detail9IntBitSetImE3AnyEv:
  182|  16.1M|    constexpr bool Any() const noexcept { return !None(); }
_ZNK13bitset_detail9IntBitSetImE4NoneEv:
  180|  20.9M|    constexpr bool None() const noexcept { return m_val == 0; }
_ZNK13bitset_detail9IntBitSetImE5FirstEv:
  189|  11.3M|    {
  190|  11.3M|        Assume(m_val != 0);
  ------------------
  |  |  128|  11.3M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  191|  11.3M|        return std::countr_zero(m_val);
  192|  11.3M|    }
_ZN13bitset_detail9IntBitSetImE9SingletonEj:
  139|  16.3M|    {
  140|  16.3M|        Assume(i < MAX_SIZE);
  ------------------
  |  |  128|  16.3M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  141|  16.3M|        return IntBitSet(I(1U) << i);
  142|  16.3M|    }
_ZN13bitset_detail9IntBitSetImE3SetEj:
  153|  21.4M|    {
  154|  21.4M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  21.4M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  155|  21.4M|        m_val |= I{1U} << pos;
  156|  21.4M|    }
_ZNK13bitset_detail9IntBitSetImEixEj:
  171|  53.7M|    {
  172|  53.7M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  53.7M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  173|  53.7M|        return (m_val >> pos) & 1U;
  174|  53.7M|    }
_ZNK13bitset_detail9IntBitSetImE10IsSubsetOfERKS1_:
  222|  4.38M|    constexpr bool IsSubsetOf(const IntBitSet& a) const noexcept { return (m_val & ~a.m_val) == 0; }
_ZNK13bitset_detail9IntBitSetImE5beginEv:
  184|  50.0M|    constexpr Iterator begin() const noexcept { return Iterator(m_val); }
_ZN13bitset_detail9IntBitSetImE8IteratorC2Em:
   87|  50.0M|        constexpr Iterator(I val) noexcept : m_val(val), m_pos(0)
   88|  50.0M|        {
   89|  50.0M|            if (m_val != 0) m_pos = std::countr_zero(m_val);
  ------------------
  |  Branch (89:17): [True: 37.5M, False: 12.4M]
  ------------------
   90|  50.0M|        }
_ZNK13bitset_detail9IntBitSetImE3endEv:
  186|  52.1M|    constexpr IteratorEnd end() const noexcept { return IteratorEnd(); }
_ZN13bitset_detaileqERKNS_9IntBitSetImE8IteratorERKNS1_11IteratorEndE:
   99|   141M|        {
  100|   141M|            return a.m_val == 0;
  101|   141M|        }
_ZNK13bitset_detail9IntBitSetImE8IteratordeEv:
  112|  96.9M|        {
  113|  96.9M|            Assume(m_val != 0);
  ------------------
  |  |  128|  96.9M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  114|  96.9M|            return m_pos;
  115|  96.9M|        }
_ZN13bitset_detail9IntBitSetImEoRERKS1_:
  200|  36.7M|    constexpr IntBitSet& operator|=(const IntBitSet& a) noexcept { m_val |= a.m_val; return *this; }
_ZN13bitset_detail9IntBitSetImE8IteratorppEv:
  104|  92.4M|        {
  105|  92.4M|            Assume(m_val != 0);
  ------------------
  |  |  128|  92.4M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  106|  92.4M|            m_val &= m_val - I{1U};
  107|  92.4M|            if (m_val != 0) m_pos = std::countr_zero(m_val);
  ------------------
  |  Branch (107:17): [True: 59.0M, False: 33.4M]
  ------------------
  108|  92.4M|            return *this;
  109|  92.4M|        }
_ZN13bitset_detail9IntBitSetImEmIERKS1_:
  204|  24.2M|    constexpr IntBitSet& operator-=(const IntBitSet& a) noexcept { m_val &= ~a.m_val; return *this; }
_ZN13bitset_detail9IntBitSetImE5ResetEj:
  165|  7.24M|    {
  166|  7.24M|        Assume(pos < MAX_SIZE);
  ------------------
  |  |  128|  7.24M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  167|  7.24M|        m_val &= ~I(I{1U} << pos);
  168|  7.24M|    }
_ZNK13bitset_detail9IntBitSetImE8OverlapsERKS1_:
  208|  9.87M|    constexpr bool Overlaps(const IntBitSet& a) const noexcept { return m_val & a.m_val; }
_ZNK13bitset_detail9IntBitSetImE5CountEv:
  176|  22.6M|    constexpr unsigned Count() const noexcept { return PopCount(m_val); }
_ZN13bitset_detail8PopCountImEEjT_:
   40|  73.9M|{
   41|  73.9M|    static_assert(std::is_integral_v<I> && std::is_unsigned_v<I> && std::numeric_limits<I>::radix == 2);
   42|  73.9M|    constexpr auto BITS = std::numeric_limits<I>::digits;
   43|       |    // Algorithms from https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation.
   44|       |    // These seem to be faster than std::popcount when compiling for non-SSE4 on x86_64.
   45|       |    if constexpr (BITS <= 32) {
   46|       |        v -= (v >> 1) & 0x55555555;
   47|       |        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
   48|       |        v = (v + (v >> 4)) & 0x0f0f0f0f;
   49|       |        if constexpr (BITS > 8) v += v >> 8;
   50|       |        if constexpr (BITS > 16) v += v >> 16;
   51|       |        return v & 0x3f;
   52|  73.9M|    } else {
   53|  73.9M|        static_assert(BITS <= 64);
   54|  73.9M|        v -= (v >> 1) & 0x5555555555555555;
   55|  73.9M|        v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333);
   56|  73.9M|        v = (v + (v >> 4)) & 0x0f0f0f0f0f0f0f0f;
   57|  73.9M|        return (v * uint64_t{0x0101010101010101}) >> 56;
   58|  73.9M|    }
   59|  73.9M|}
_ZN13bitset_detail9IntBitSetImEaNERKS1_:
  202|  2.79M|    constexpr IntBitSet& operator&=(const IntBitSet& a) noexcept { m_val &= a.m_val; return *this; }
_ZN13bitset_detailanERKNS_9IntBitSetImEES3_:
  210|  9.72M|    friend constexpr IntBitSet operator&(const IntBitSet& a, const IntBitSet& b) noexcept { return I(a.m_val & b.m_val); }
_ZNK13bitset_detail9IntBitSetImE12IsSupersetOfERKS1_:
  220|   563k|    constexpr bool IsSupersetOf(const IntBitSet& a) const noexcept { return (a.m_val & ~m_val) == 0; }

_ZN10btcsignals6signalIFvvENS_10null_valueEED2Ev:
  175|      6|    ~signal() = default;
_ZN10btcsignals6signalIFv20SynchronizationStatellbENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFv20SynchronizationStateRK11CBlockIndexdENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFvRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEibENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFvbENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFviENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFvRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFbRK13bilingual_strRKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEjENS_6any_ofEED2Ev:
  175|      2|    ~signal() = default;
_ZN10btcsignals6signalIFvRK13bilingual_strjENS_10null_valueEED2Ev:
  175|      2|    ~signal() = default;

_Z22inline_assertion_checkILb1ERPKNSt3__18functionIFvNS0_4spanIKhLm18446744073709551615EEEEEEEOT0_SB_RKNS0_15source_locationENS0_17basic_string_viewIcNS0_11char_traitsIcEEEE:
   90|  18.0k|{
   91|  18.0k|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [True: 18.0k, Folded]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  18.0k|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 18.0k]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  18.0k|    }
   96|  18.0k|    return std::forward<T>(val);
   97|  18.0k|}
_Z22inline_assertion_checkILb1ERNSt3__16atomicIbEEEOT0_S5_RKNS0_15source_locationENS0_17basic_string_viewIcNS0_11char_traitsIcEEEE:
   90|  18.0k|{
   91|  18.0k|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [True: 18.0k, Folded]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  18.0k|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 18.0k]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  18.0k|    }
   96|  18.0k|    return std::forward<T>(val);
   97|  18.0k|}
_Z21EnableFuzzDeterminismv:
   39|  18.0k|{
   40|  18.0k|    if constexpr (G_FUZZING_BUILD) {
   41|  18.0k|        return true;
   42|       |    } else if constexpr (!G_ABORT_ON_FAILED_ASSUME) {
   43|       |        // Running fuzz tests is always disabled if Assume() doesn't abort
   44|       |        // (ie, non-fuzz non-debug builds), as otherwise tests which
   45|       |        // should fail due to a failing Assume may still pass. As such,
   46|       |        // we also statically disable fuzz determinism in that case.
   47|       |        return false;
   48|       |    } else {
   49|       |        return g_enable_dynamic_fuzz_determinism;
   50|       |    }
   51|  18.0k|}
_Z22inline_assertion_checkILb0EjEOT0_S1_RKNSt3__115source_locationENS2_17basic_string_viewIcNS2_11char_traitsIcEEEE:
   90|  8.58M|{
   91|  8.58M|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  8.58M|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 8.58M]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  8.58M|    }
   96|  8.58M|    return std::forward<T>(val);
   97|  8.58M|}
_Z22inline_assertion_checkILb0ERmEOT0_S2_RKNSt3__115source_locationENS3_17basic_string_viewIcNS3_11char_traitsIcEEEE:
   90|  4.09M|{
   91|  4.09M|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  4.09M|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 4.09M]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  4.09M|    }
   96|  4.09M|    return std::forward<T>(val);
   97|  4.09M|}
_Z22inline_assertion_checkILb0ERPKN7TxGraph3RefEEOT0_S6_RKNSt3__115source_locationENS7_17basic_string_viewIcNS7_11char_traitsIcEEEE:
   90|   736k|{
   91|   736k|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|   736k|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 736k]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|   736k|    }
   96|   736k|    return std::forward<T>(val);
   97|   736k|}
txgraph.cpp:_Z22inline_assertion_checkILb0ERPN12_GLOBAL__N_17ClusterEEOT0_S5_RKNSt3__115source_locationENS6_17basic_string_viewIcNS6_11char_traitsIcEEEE:
   90|   119k|{
   91|   119k|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|   119k|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 119k]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|   119k|    }
   96|   119k|    return std::forward<T>(val);
   97|   119k|}
_Z22inline_assertion_checkILb0ERbEOT0_S2_RKNSt3__115source_locationENS3_17basic_string_viewIcNS3_11char_traitsIcEEEE:
   90|  1.25M|{
   91|  1.25M|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  1.25M|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 1.25M]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  1.25M|    }
   96|  1.25M|    return std::forward<T>(val);
   97|  1.25M|}
_Z22inline_assertion_checkILb1EbEOT0_S1_RKNSt3__115source_locationENS2_17basic_string_viewIcNS2_11char_traitsIcEEEE:
   90|  36.0k|{
   91|  36.0k|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [True: 36.0k, Folded]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  36.0k|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 36.0k]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  36.0k|    }
   96|  36.0k|    return std::forward<T>(val);
   97|  36.0k|}
_Z22inline_assertion_checkILb0EbEOT0_S1_RKNSt3__115source_locationENS2_17basic_string_viewIcNS2_11char_traitsIcEEEE:
   90|  2.22G|{
   91|  2.22G|    if (IS_ASSERT || std::is_constant_evaluated() || G_ABORT_ON_FAILED_ASSUME) {
  ------------------
  |  Branch (91:9): [Folded, False: 0]
  |  Branch (91:22): [Folded, False: 0]
  |  Branch (91:54): [True: 0, Folded]
  ------------------
   92|  2.22G|        if (!val) {
  ------------------
  |  Branch (92:13): [True: 0, False: 2.22G]
  ------------------
   93|      0|            assertion_fail(loc, assertion);
   94|      0|        }
   95|  2.22G|    }
   96|  2.22G|    return std::forward<T>(val);
   97|  2.22G|}

_Z13CompareChunksNSt3__14spanIK7FeeFracLm18446744073709551615EEES3_:
   13|  17.8k|{
   14|       |    /** Array to allow indexed access to input diagrams. */
   15|  17.8k|    const std::array<std::span<const FeeFrac>, 2> chunk = {chunks0, chunks1};
   16|       |    /** How many elements we have processed in each input. */
   17|  17.8k|    size_t next_index[2] = {0, 0};
   18|       |    /** Accumulated fee/sizes in diagrams, up to next_index[i] - 1. */
   19|  17.8k|    FeeFrac accum[2];
   20|       |    /** Whether the corresponding input is strictly better than the other at least in one place. */
   21|  17.8k|    bool better_somewhere[2] = {false, false};
   22|       |    /** Get the first unprocessed point in diagram number dia. */
   23|  17.8k|    const auto next_point = [&](int dia) { return chunk[dia][next_index[dia]] + accum[dia]; };
   24|       |    /** Get the last processed point in diagram number dia. */
   25|  17.8k|    const auto prev_point = [&](int dia) { return accum[dia]; };
   26|       |    /** Move to the next point in diagram number dia. */
   27|  17.8k|    const auto advance = [&](int dia) { accum[dia] += chunk[dia][next_index[dia]++]; };
   28|       |
   29|   527k|    do {
   30|   527k|        bool done_0 = next_index[0] == chunk[0].size();
   31|   527k|        bool done_1 = next_index[1] == chunk[1].size();
   32|   527k|        if (done_0 && done_1) break;
  ------------------
  |  Branch (32:13): [True: 17.8k, False: 509k]
  |  Branch (32:23): [True: 17.8k, False: 0]
  ------------------
   33|       |
   34|       |        // Determine which diagram has the first unprocessed point. If a single side is finished, use the
   35|       |        // other one. Only up to one can be done due to check above.
   36|   509k|        const int unproc_side = (done_0 || done_1) ? done_0 : next_point(0).size > next_point(1).size;
  ------------------
  |  Branch (36:34): [True: 0, False: 509k]
  |  Branch (36:44): [True: 0, False: 509k]
  ------------------
   37|       |
   38|       |        // Let `P` be the next point on diagram unproc_side, and `A` and `B` the previous and next points
   39|       |        // on the other diagram. We want to know if P lies above or below the line AB. To determine this, we
   40|       |        // compute the slopes of line AB and of line AP, and compare them. These slopes are fee per size,
   41|       |        // and can thus be expressed as FeeFracs.
   42|   509k|        const FeeFrac& point_p = next_point(unproc_side);
   43|   509k|        const FeeFrac& point_a = prev_point(!unproc_side);
   44|       |
   45|   509k|        const auto slope_ap = point_p - point_a;
   46|   509k|        Assume(slope_ap.size > 0);
  ------------------
  |  |  128|   509k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   47|   509k|        auto cmp = std::strong_ordering::equivalent;
   48|   509k|        if (done_0 || done_1) {
  ------------------
  |  Branch (48:13): [True: 0, False: 509k]
  |  Branch (48:23): [True: 0, False: 509k]
  ------------------
   49|       |            // If a single side has no points left, act as if AB has slope tail_feerate(of 0).
   50|      0|            Assume(!(done_0 && done_1));
  ------------------
  |  |  128|      0|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 0, False: 0]
  |  |  |  Branch (128:51): [True: 0, False: 0]
  |  |  ------------------
  ------------------
   51|      0|            cmp = ByRatio{slope_ap} <=> ByRatio{FeeFrac(0, 1)};
   52|   509k|        } else {
   53|       |            // If both sides have points left, compute B, and the slope of AB explicitly.
   54|   509k|            const FeeFrac& point_b = next_point(!unproc_side);
   55|   509k|            const auto slope_ab = point_b - point_a;
   56|   509k|            Assume(slope_ab.size >= slope_ap.size);
  ------------------
  |  |  128|   509k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   57|   509k|            cmp = ByRatio{slope_ap} <=> ByRatio{slope_ab};
   58|       |
   59|       |            // If B and P have the same size, B can be marked as processed (in addition to P, see
   60|       |            // below), as we've already performed a comparison at this size.
   61|   509k|            if (point_b.size == point_p.size) advance(!unproc_side);
  ------------------
  |  Branch (61:17): [True: 470k, False: 39.1k]
  ------------------
   62|   509k|        }
   63|       |        // If P lies above AB, unproc_side is better in P. If P lies below AB, then !unproc_side is
   64|       |        // better in P.
   65|   509k|        if (std::is_gt(cmp)) better_somewhere[unproc_side] = true;
  ------------------
  |  Branch (65:13): [True: 0, False: 509k]
  ------------------
   66|   509k|        if (std::is_lt(cmp)) better_somewhere[!unproc_side] = true;
  ------------------
  |  Branch (66:13): [True: 0, False: 509k]
  ------------------
   67|   509k|        advance(unproc_side);
   68|       |
   69|       |        // If both diagrams are better somewhere, they are incomparable.
   70|   509k|        if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered;
  ------------------
  |  Branch (70:13): [True: 0, False: 509k]
  |  Branch (70:36): [True: 0, False: 0]
  ------------------
   71|   509k|    } while(true);
  ------------------
  |  Branch (71:13): [True: 509k, Folded]
  ------------------
   72|       |
   73|       |    // Otherwise compare the better_somewhere values.
   74|  17.8k|    return better_somewhere[0] <=> better_somewhere[1];
   75|  17.8k|}
feefrac.cpp:_ZZ13CompareChunksNSt3__14spanIK7FeeFracLm18446744073709551615EEES3_ENK3$_0clEi:
   23|  2.03M|    const auto next_point = [&](int dia) { return chunk[dia][next_index[dia]] + accum[dia]; };
feefrac.cpp:_ZZ13CompareChunksNSt3__14spanIK7FeeFracLm18446744073709551615EEES3_ENK3$_1clEi:
   25|   509k|    const auto prev_point = [&](int dia) { return accum[dia]; };
feefrac.cpp:_ZZ13CompareChunksNSt3__14spanIK7FeeFracLm18446744073709551615EEES3_ENK3$_2clEi:
   27|   979k|    const auto advance = [&](int dia) { accum[dia] += chunk[dia][next_index[dia]++]; };

_ZleRK7ByRatioI7FeeFracES3_:
  257|  1.96M|    {
  258|  1.96M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  259|  1.96M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  260|  1.96M|        return cross_a <= cross_b;
  261|  1.96M|    }
_ZleRK7ByRatioI10FeePerUnitI9WeightTagEES5_:
  257|   460k|    {
  258|   460k|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  259|   460k|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  260|   460k|        return cross_a <= cross_b;
  261|   460k|    }
_ZgeRK7ByRatioI10FeePerUnitI9WeightTagEES5_:
  263|   456k|    {
  264|   456k|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  265|   456k|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  266|   456k|        return cross_a >= cross_b;
  267|   456k|    }
_ZplRK7FeeFracS1_:
  122|  3.87M|    {
  123|  3.87M|        return {a.fee + b.fee, a.size + b.size};
  124|  3.87M|    }
_ZmiRK7FeeFracS1_:
  128|  1.09M|    {
  129|  1.09M|        return {a.fee - b.fee, a.size - b.size};
  130|  1.09M|    }
_ZssRK7ByRatioI10FeePerUnitI9WeightTagEES5_:
  236|  80.9M|    {
  237|  80.9M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  238|  80.9M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  239|  80.9M|        return cross_a <=> cross_b;
  240|  80.9M|    }
_ZN7ByRatioI10FeePerUnitI9WeightTagEEC2ERKS2_:
  223|   163M|    constexpr ByRatio(const T& feefrac) noexcept : m_feefrac{feefrac} {}
_ZN10FeePerUnitI9WeightTagE11FromFeeFracERK7FeeFrac:
  198|  4.10M|    {
  199|  4.10M|        return {feefrac.fee, feefrac.size};
  200|  4.10M|    }
_ZgtRK7ByRatioI7FeeFracES3_:
  251|  33.2M|    {
  252|  33.2M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  253|  33.2M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  254|  33.2M|        return cross_a > cross_b;
  255|  33.2M|    }
_ZltRK7ByRatioI7FeeFracES3_:
  245|  1.84M|    {
  246|  1.84M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  247|  1.84M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  248|  1.84M|        return cross_a < cross_b;
  249|  1.84M|    }
_ZN7ByRatioI7FeeFracEC2ERKS0_:
  223|   103M|    constexpr ByRatio(const T& feefrac) noexcept : m_feefrac{feefrac} {}
_ZssRK7ByRatioI7FeeFracES3_:
  236|  13.6M|    {
  237|  13.6M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  238|  13.6M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  239|  13.6M|        return cross_a <=> cross_b;
  240|  13.6M|    }
_ZN7FeeFracmIERKS_:
  115|   614k|    {
  116|   614k|        fee -= other.fee;
  117|   614k|        size -= other.size;
  118|   614k|    }
_ZNK7FeeFrac7IsEmptyEv:
  102|  1.35M|    bool inline IsEmpty() const noexcept {
  103|  1.35M|        return size == 0;
  104|  1.35M|    }
_ZeqRK7ByRatioI7FeeFracES3_:
  226|   153k|    {
  227|   153k|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  228|   153k|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  229|   153k|        return cross_a == cross_b;
  230|   153k|    }
_ZssRK14ByRatioNegSizeI7FeeFracES3_:
  302|   170M|    {
  303|   170M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  304|   170M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  305|   170M|        auto cmp = cross_a <=> cross_b;
  306|   170M|        if (cmp != 0) return cmp;
  ------------------
  |  Branch (306:13): [True: 57.2M, False: 113M]
  ------------------
  307|   113M|        return b.m_feefrac.size <=> a.m_feefrac.size;
  308|   170M|    }
_ZN14ByRatioNegSizeI7FeeFracEC2ERKS0_:
  294|   340M|    constexpr ByRatioNegSize(const T& feefrac) noexcept : m_feefrac{feefrac} {}
_Z4swapR7FeeFracS0_:
  140|   579k|    {
  141|   579k|        std::swap(a.fee, b.fee);
  142|   579k|        std::swap(a.size, b.size);
  143|   579k|    }
_ZssRK14ByRatioNegSizeI10FeePerUnitI9WeightTagEES5_:
  302|  8.87M|    {
  303|  8.87M|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  304|  8.87M|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  305|  8.87M|        auto cmp = cross_a <=> cross_b;
  306|  8.87M|        if (cmp != 0) return cmp;
  ------------------
  |  Branch (306:13): [True: 3.35M, False: 5.52M]
  ------------------
  307|  5.52M|        return b.m_feefrac.size <=> a.m_feefrac.size;
  308|  8.87M|    }
_ZN14ByRatioNegSizeI10FeePerUnitI9WeightTagEEC2ERKS2_:
  294|  17.7M|    constexpr ByRatioNegSize(const T& feefrac) noexcept : m_feefrac{feefrac} {}
_ZgeRK7ByRatioI7FeeFracES3_:
  263|   855k|    {
  264|   855k|        auto cross_a = T::Mul(a.m_feefrac.fee, b.m_feefrac.size);
  265|   855k|        auto cross_b = T::Mul(b.m_feefrac.fee, a.m_feefrac.size);
  266|   855k|        return cross_a >= cross_b;
  267|   855k|    }
_ZN7FeeFracC2Ev:
   93|  15.4M|    constexpr inline FeeFrac() noexcept : fee{0}, size{0} {}
_ZeqRK7FeeFracS1_:
  134|  16.6M|    {
  135|  16.6M|        return a.fee == b.fee && a.size == b.size;
  ------------------
  |  Branch (135:16): [True: 16.6M, False: 0]
  |  Branch (135:34): [True: 16.6M, False: 0]
  ------------------
  136|  16.6M|    }
_ZN7FeeFracpLERKS_:
  108|  28.5M|    {
  109|  28.5M|        fee += other.fee;
  110|  28.5M|        size += other.size;
  111|  28.5M|    }
_ZN7FeeFracC2Eli:
   96|  9.85M|    constexpr inline FeeFrac(int64_t f, int32_t s) noexcept : fee{f}, size{s} {}
_ZN7FeeFrac3MulEli:
   66|   625M|    {
   67|   625M|        return __int128{a} * b;
   68|   625M|    }

_ZN4util3log22LogPrintFormatInternalIJPKcNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEEEEvO14SourceLocationN5BCLog8LogFlagsENS0_5LevelENS_21ConstevalFormatStringIXsZT_EEEDpRKT_:
  101|  18.0k|{
  102|  18.0k|    return LogPrintFormatInternal_(std::move(source_loc), flag, level, /*should_ratelimit=*/true, fmt, args...);
  103|  18.0k|}
_ZN4util3log23LogPrintFormatInternal_IJPKcNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEEEEvO14SourceLocationN5BCLog8LogFlagsENS0_5LevelEbNS_21ConstevalFormatStringIXsZT_EEEDpRKT_:
   84|  18.0k|{
   85|  18.0k|    std::string log_msg;
   86|  18.0k|    try {
   87|  18.0k|        log_msg = tfm::format(fmt, args...);
   88|  18.0k|    } catch (tinyformat::format_error& fmterr) {
   89|      0|        log_msg = "Error \"" + std::string{fmterr.what()} + "\" while formatting log message: " + fmt.fmt;
   90|      0|    }
   91|  18.0k|    util::log::Log(util::log::Entry{
   92|  18.0k|        .category = flag,
   93|  18.0k|        .level = level,
   94|  18.0k|        .should_ratelimit = should_ratelimit,
   95|  18.0k|        .source_loc = std::move(source_loc),
   96|  18.0k|        .message = std::move(log_msg)});
   97|  18.0k|}
_ZN14SourceLocationC2EPKcNSt3__115source_locationE:
   31|  18.0k|        : m_func{func}, m_loc{loc} {}

_ZN8StdMutex12CheckNotHeldERS_:
   37|  36.0k|    static inline StdMutex& CheckNotHeld(StdMutex& cs) EXCLUSIVE_LOCKS_REQUIRED(!cs) LOCK_RETURNED(cs) { return cs; }
_ZN8StdMutex5GuardC2ERS_:
   33|  36.0k|        explicit Guard(StdMutex& cs) EXCLUSIVE_LOCK_FUNCTION(cs) : std::lock_guard<StdMutex>(cs) {}

_ZN16CThreadInterruptD2Ev:
   32|      4|    virtual ~CThreadInterrupt() = default;

_ZN4util21ThreadGetInternalNameEv:
   56|  18.0k|std::string util::ThreadGetInternalName() { return g_thread_name; }

_ZN10ThreadPoolD2Ev:
   93|     10|    {
   94|     10|        Stop(); // In case it hasn't been stopped.
   95|     10|    }
_ZN10ThreadPool4StopEv:
  129|     10|    {
  130|       |        // Notify workers and join them
  131|     10|        std::vector<std::thread> threads_to_join;
  132|     10|        {
  133|     10|            LOCK(m_mutex);
  ------------------
  |  |  268|     10|#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
  |  |  ------------------
  |  |  |  |   11|     10|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|     10|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|     10|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  134|       |            // Ensure Stop() is not called from a worker thread while workers are still registered,
  135|       |            // otherwise a self-join deadlock would occur.
  136|     10|            auto id = std::this_thread::get_id();
  137|     10|            for (const auto& worker : m_workers) assert(worker.get_id() != id);
  ------------------
  |  Branch (137:37): [True: 0, False: 10]
  |  Branch (137:50): [True: 0, False: 0]
  ------------------
  138|       |            // Early shutdown to return right away on any concurrent Submit() call
  139|     10|            m_interrupt = true;
  140|     10|            threads_to_join.swap(m_workers);
  141|     10|        }
  142|      0|        m_cv.notify_all();
  143|       |        // Help draining queue
  144|     10|        while (ProcessTask()) {}
  ------------------
  |  Branch (144:16): [True: 0, False: 10]
  ------------------
  145|       |        // Free resources
  146|     10|        for (auto& worker : threads_to_join) worker.join();
  ------------------
  |  Branch (146:27): [True: 0, False: 10]
  ------------------
  147|       |
  148|       |        // Since we currently wait for tasks completion, sanity-check empty queue
  149|     10|        LOCK(m_mutex);
  ------------------
  |  |  268|     10|#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
  |  |  ------------------
  |  |  |  |   11|     10|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|     10|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|     10|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  150|     10|        Assume(m_work_queue.empty());
  ------------------
  |  |  128|     10|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  151|       |        // Re-allow Start() now that all workers have exited
  152|     10|        m_interrupt = false;
  153|     10|    }
_ZN10ThreadPool11ProcessTaskEv:
  244|     10|    {
  245|     10|        std::packaged_task<void()> task;
  246|     10|        {
  247|     10|            LOCK(m_mutex);
  ------------------
  |  |  268|     10|#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
  |  |  ------------------
  |  |  |  |   11|     10|#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
  |  |  |  |  ------------------
  |  |  |  |  |  |    9|     10|#define PASTE2(x, y) PASTE(x, y)
  |  |  |  |  |  |  ------------------
  |  |  |  |  |  |  |  |    8|     10|#define PASTE(x, y) x ## y
  |  |  |  |  |  |  ------------------
  |  |  |  |  ------------------
  |  |  ------------------
  ------------------
  248|     10|            if (m_work_queue.empty()) return false;
  ------------------
  |  Branch (248:17): [True: 10, False: 0]
  ------------------
  249|       |
  250|       |            // Pop the task
  251|      0|            task = std::move(m_work_queue.front());
  252|      0|            m_work_queue.pop();
  253|      0|        }
  254|      0|        task();
  255|      0|        return true;
  256|     10|    }

_Z11SetMockTimeNSt3__16chrono8durationIxNS_5ratioILl1ELl1EEEEE:
   54|  18.0k|{
   55|  18.0k|    Assert(mock_time_in >= 0s);
  ------------------
  |  |  116|  18.0k|#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
  ------------------
   56|  18.0k|    g_mock_time.store(mock_time_in, std::memory_order_relaxed);
   57|  18.0k|}
_Z11GetMockTimev:
   60|  18.0k|{
   61|  18.0k|    return g_mock_time.load(std::memory_order_relaxed);
   62|  18.0k|}
_ZN19MockableSteadyClock13ClearMockTimeEv:
   84|  18.0k|{
   85|  18.0k|    g_mock_steady_time.store(0ms, std::memory_order_relaxed);
   86|  18.0k|}

_ZN8VecDequeIhEC2Ev:
  107|   227k|    VecDeque() noexcept = default;
_ZN8VecDequeINSt3__15tupleIJhjjEEEEC2Ev:
  107|   227k|    VecDeque() noexcept = default;
_ZN8VecDequeIhE7reserveEm:
  207|   227k|    {
  208|   227k|        if (capacity > m_capacity) Reallocate(capacity);
  ------------------
  |  Branch (208:13): [True: 227k, False: 95]
  ------------------
  209|   227k|    }
_ZN8VecDequeIhE10ReallocateEm:
   40|   454k|    {
   41|   454k|        Assume(capacity >= m_size);
  ------------------
  |  |  128|   454k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   42|   454k|        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
  ------------------
  |  |  128|  1.41M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 276k, False: 177k]
  |  |  |  Branch (128:51): [True: 227k, False: 49.5k]
  |  |  |  Branch (128:51): [True: 227k, False: 0]
  |  |  ------------------
  ------------------
   43|       |        // Allocate new buffer.
   44|   454k|        T* new_buffer = capacity ? std::allocator<T>().allocate(capacity) : nullptr;
  ------------------
  |  Branch (44:25): [True: 227k, False: 227k]
  ------------------
   45|   454k|        if (capacity) {
  ------------------
  |  Branch (45:13): [True: 227k, False: 227k]
  ------------------
   46|   227k|            if constexpr (std::is_trivially_copyable_v<T>) {
   47|       |                // When T is trivially copyable, just copy the data over from old to new buffer.
   48|   227k|                size_t first_part = FirstPart();
   49|   227k|                if (first_part != 0) {
  ------------------
  |  Branch (49:21): [True: 0, False: 227k]
  ------------------
   50|      0|                    std::memcpy(new_buffer, m_buffer + m_offset, first_part * sizeof(T));
   51|      0|                }
   52|   227k|                if (first_part != m_size) {
  ------------------
  |  Branch (52:21): [True: 0, False: 227k]
  ------------------
   53|      0|                    std::memcpy(new_buffer + first_part, m_buffer, (m_size - first_part) * sizeof(T));
   54|      0|                }
   55|       |            } else {
   56|       |                // Otherwise move-construct in place in the new buffer, and destroy old buffer objects.
   57|       |                size_t old_pos = m_offset;
   58|       |                for (size_t new_pos = 0; new_pos < m_size; ++new_pos) {
   59|       |                    std::construct_at(new_buffer + new_pos, std::move(*(m_buffer + old_pos)));
   60|       |                    std::destroy_at(m_buffer + old_pos);
   61|       |                    ++old_pos;
   62|       |                    if (old_pos == m_capacity) old_pos = 0;
   63|       |                }
   64|       |            }
   65|   227k|        }
   66|       |        // Deallocate old buffer and update housekeeping.
   67|   454k|        std::allocator<T>().deallocate(m_buffer, m_capacity);
   68|   454k|        m_buffer = new_buffer;
   69|   454k|        m_offset = 0;
   70|   454k|        m_capacity = capacity;
   71|   454k|        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
  ------------------
  |  |  128|  1.58M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 454k, False: 0]
  |  |  |  Branch (128:51): [True: 227k, False: 227k]
  |  |  |  Branch (128:51): [True: 227k, False: 0]
  |  |  ------------------
  ------------------
   72|   454k|    }
_ZNK8VecDequeIhE9FirstPartEv:
   37|   227k|    size_t FirstPart() const noexcept { return std::min(m_capacity - m_offset, m_size); }
_ZNK8VecDequeIhE5emptyEv:
  310|  1.73M|    bool empty() const noexcept { return m_size == 0; }
_ZN8VecDequeIhE12emplace_backIJRjEEEvDpOT_:
  220|   633k|    {
  221|   633k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 633k]
  ------------------
  222|   633k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|   633k|        ++m_size;
  224|   633k|    }
_ZNK8VecDequeIhE11BufferIndexEm:
   76|  1.88M|    {
   77|  1.88M|        Assume(pos < m_capacity);
  ------------------
  |  |  128|  1.88M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   78|       |        // The expression below is used instead of the more obvious (pos + m_offset >= m_capacity),
   79|       |        // because the addition there could in theory overflow with very large deques.
   80|  1.88M|        if (pos >= m_capacity - m_offset) {
  ------------------
  |  Branch (80:13): [True: 33.1k, False: 1.85M]
  ------------------
   81|  33.1k|            return (m_offset + pos) - m_capacity;
   82|  1.85M|        } else {
   83|  1.85M|            return m_offset + pos;
   84|  1.85M|        }
   85|  1.88M|    }
_ZNK8VecDequeIhE4sizeEv:
  312|  1.91M|    size_t size() const noexcept { return m_size; }
_ZN8VecDequeIhE4backEv:
  283|   461k|    {
  284|   461k|        Assume(m_size);
  ------------------
  |  |  128|   461k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  285|   461k|        return m_buffer[BufferIndex(m_size - 1)];
  286|   461k|    }
_ZN8VecDequeIhEixEm:
  297|   461k|    {
  298|   461k|        Assume(idx < m_size);
  ------------------
  |  |  128|   461k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  299|   461k|        return m_buffer[BufferIndex(idx)];
  300|   461k|    }
_ZN8VecDequeIhE5frontEv:
  269|   950k|    {
  270|   950k|        Assume(m_size);
  ------------------
  |  |  128|   950k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  271|   950k|        return m_buffer[m_offset];
  272|   950k|    }
_ZN8VecDequeIhE9pop_frontEv:
  251|   950k|    {
  252|   950k|        Assume(m_size);
  ------------------
  |  |  128|   950k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  253|   950k|        std::destroy_at(m_buffer + m_offset);
  254|   950k|        --m_size;
  255|   950k|        ++m_offset;
  256|   950k|        if (m_offset == m_capacity) m_offset = 0;
  ------------------
  |  Branch (256:13): [True: 57.4k, False: 892k]
  ------------------
  257|   950k|    }
_ZN8VecDequeIhE9push_backERKh:
  230|  49.0k|    void push_back(const T& elem) { emplace_back(elem); }
_ZN8VecDequeIhE12emplace_backIJRKhEEEvDpOT_:
  220|  49.0k|    {
  221|  49.0k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 49.0k]
  ------------------
  222|  49.0k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|  49.0k|        ++m_size;
  224|  49.0k|    }
_ZN8VecDequeIhE9push_backEOh:
  227|   276k|    void push_back(T&& elem) { emplace_back(std::move(elem)); }
_ZN8VecDequeIhE12emplace_backIJhEEEvDpOT_:
  220|   276k|    {
  221|   276k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 276k]
  ------------------
  222|   276k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|   276k|        ++m_size;
  224|   276k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE5clearEv:
  126|   317k|    void clear() noexcept { ResizeDown(0); }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE10ResizeDownEm:
   90|   317k|    {
   91|   317k|        Assume(size <= m_size);
  ------------------
  |  |  128|   317k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   92|   317k|        if constexpr (std::is_trivially_destructible_v<T>) {
   93|       |            // If T is trivially destructible, we do not need to do anything but update the
   94|       |            // housekeeping record. Default constructor or zero-filling will be used when
   95|       |            // the space is reused.
   96|   317k|            m_size = size;
   97|       |        } else {
   98|       |            // If not, we need to invoke the destructor for every element separately.
   99|       |            while (m_size > size) {
  100|       |                std::destroy_at(m_buffer + BufferIndex(m_size - 1));
  101|       |                --m_size;
  102|       |            }
  103|       |        }
  104|   317k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE7reserveEm:
  207|  90.0k|    {
  208|  90.0k|        if (capacity > m_capacity) Reallocate(capacity);
  ------------------
  |  Branch (208:13): [True: 89.9k, False: 95]
  ------------------
  209|  90.0k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE10ReallocateEm:
   40|   317k|    {
   41|   317k|        Assume(capacity >= m_size);
  ------------------
  |  |  128|   317k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   42|   317k|        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
  ------------------
  |  |  128|   976k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 252k, False: 64.7k]
  |  |  |  Branch (128:51): [True: 227k, False: 25.1k]
  |  |  |  Branch (128:51): [True: 89.9k, False: 0]
  |  |  ------------------
  ------------------
   43|       |        // Allocate new buffer.
   44|   317k|        T* new_buffer = capacity ? std::allocator<T>().allocate(capacity) : nullptr;
  ------------------
  |  Branch (44:25): [True: 89.9k, False: 227k]
  ------------------
   45|   317k|        if (capacity) {
  ------------------
  |  Branch (45:13): [True: 89.9k, False: 227k]
  ------------------
   46|       |            if constexpr (std::is_trivially_copyable_v<T>) {
   47|       |                // When T is trivially copyable, just copy the data over from old to new buffer.
   48|       |                size_t first_part = FirstPart();
   49|       |                if (first_part != 0) {
   50|       |                    std::memcpy(new_buffer, m_buffer + m_offset, first_part * sizeof(T));
   51|       |                }
   52|       |                if (first_part != m_size) {
   53|       |                    std::memcpy(new_buffer + first_part, m_buffer, (m_size - first_part) * sizeof(T));
   54|       |                }
   55|  89.9k|            } else {
   56|       |                // Otherwise move-construct in place in the new buffer, and destroy old buffer objects.
   57|  89.9k|                size_t old_pos = m_offset;
   58|  89.9k|                for (size_t new_pos = 0; new_pos < m_size; ++new_pos) {
  ------------------
  |  Branch (58:42): [True: 0, False: 89.9k]
  ------------------
   59|      0|                    std::construct_at(new_buffer + new_pos, std::move(*(m_buffer + old_pos)));
   60|      0|                    std::destroy_at(m_buffer + old_pos);
   61|      0|                    ++old_pos;
   62|      0|                    if (old_pos == m_capacity) old_pos = 0;
  ------------------
  |  Branch (62:25): [True: 0, False: 0]
  ------------------
   63|      0|                }
   64|  89.9k|            }
   65|  89.9k|        }
   66|       |        // Deallocate old buffer and update housekeeping.
   67|   317k|        std::allocator<T>().deallocate(m_buffer, m_capacity);
   68|   317k|        m_buffer = new_buffer;
   69|   317k|        m_offset = 0;
   70|   317k|        m_capacity = capacity;
   71|   317k|        Assume((m_offset == 0 && m_capacity == 0) || m_offset < m_capacity);
  ------------------
  |  |  128|  1.04M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  |  |  ------------------
  |  |  |  Branch (128:51): [True: 317k, False: 0]
  |  |  |  Branch (128:51): [True: 227k, False: 89.9k]
  |  |  |  Branch (128:51): [True: 89.9k, False: 0]
  |  |  ------------------
  ------------------
   72|   317k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE12emplace_backIJRjS5_mEEEvDpOT_:
  220|   268k|    {
  221|   268k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 268k]
  ------------------
  222|   268k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|   268k|        ++m_size;
  224|   268k|    }
_ZNK8VecDequeINSt3__15tupleIJhjjEEEE11BufferIndexEm:
   76|  1.27M|    {
   77|  1.27M|        Assume(pos < m_capacity);
  ------------------
  |  |  128|  1.27M|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   78|       |        // The expression below is used instead of the more obvious (pos + m_offset >= m_capacity),
   79|       |        // because the addition there could in theory overflow with very large deques.
   80|  1.27M|        if (pos >= m_capacity - m_offset) {
  ------------------
  |  Branch (80:13): [True: 104k, False: 1.16M]
  ------------------
   81|   104k|            return (m_offset + pos) - m_capacity;
   82|  1.16M|        } else {
   83|  1.16M|            return m_offset + pos;
   84|  1.16M|        }
   85|  1.27M|    }
_ZNK8VecDequeINSt3__15tupleIJhjjEEEE4sizeEv:
  312|   738k|    size_t size() const noexcept { return m_size; }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE4backEv:
  283|   248k|    {
  284|   248k|        Assume(m_size);
  ------------------
  |  |  128|   248k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  285|   248k|        return m_buffer[BufferIndex(m_size - 1)];
  286|   248k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEEixEm:
  297|   248k|    {
  298|   248k|        Assume(idx < m_size);
  ------------------
  |  |  128|   248k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  299|   248k|        return m_buffer[BufferIndex(idx)];
  300|   248k|    }
_ZNK8VecDequeINSt3__15tupleIJhjjEEEE5emptyEv:
  310|   821k|    bool empty() const noexcept { return m_size == 0; }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE5frontEv:
  269|   742k|    {
  270|   742k|        Assume(m_size);
  ------------------
  |  |  128|   742k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  271|   742k|        return m_buffer[m_offset];
  272|   742k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE9pop_frontEv:
  251|   742k|    {
  252|   742k|        Assume(m_size);
  ------------------
  |  |  128|   742k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
  253|   742k|        std::destroy_at(m_buffer + m_offset);
  254|   742k|        --m_size;
  255|   742k|        ++m_offset;
  256|   742k|        if (m_offset == m_capacity) m_offset = 0;
  ------------------
  |  Branch (256:13): [True: 76.0k, False: 666k]
  ------------------
  257|   742k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE12emplace_backIJRhRjS6_EEEvDpOT_:
  220|   290k|    {
  221|   290k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 290k]
  ------------------
  222|   290k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|   290k|        ++m_size;
  224|   290k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEE12emplace_backIJRhRjmEEEvDpOT_:
  220|   215k|    {
  221|   215k|        if (m_size == m_capacity) Reallocate((m_size + 1) * 2);
  ------------------
  |  Branch (221:13): [True: 0, False: 215k]
  ------------------
  222|   215k|        std::construct_at(m_buffer + BufferIndex(m_size), std::forward<Args>(args)...);
  223|   215k|        ++m_size;
  224|   215k|    }
_ZN8VecDequeINSt3__15tupleIJhjjEEEED2Ev:
  130|   227k|    {
  131|   227k|        clear();
  132|   227k|        Reallocate(0);
  133|   227k|    }
_ZN8VecDequeIhED2Ev:
  130|   227k|    {
  131|   227k|        clear();
  132|   227k|        Reallocate(0);
  133|   227k|    }
_ZN8VecDequeIhE5clearEv:
  126|   227k|    void clear() noexcept { ResizeDown(0); }
_ZN8VecDequeIhE10ResizeDownEm:
   90|   227k|    {
   91|   227k|        Assume(size <= m_size);
  ------------------
  |  |  128|   227k|#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
  ------------------
   92|   227k|        if constexpr (std::is_trivially_destructible_v<T>) {
   93|       |            // If T is trivially destructible, we do not need to do anything but update the
   94|       |            // housekeeping record. Default constructor or zero-filling will be used when
   95|       |            // the space is reused.
   96|   227k|            m_size = size;
   97|       |        } else {
   98|       |            // If not, we need to invoke the destructor for every element separately.
   99|       |            while (m_size > size) {
  100|       |                std::destroy_at(m_buffer + BufferIndex(m_size - 1));
  101|       |                --m_size;
  102|       |            }
  103|       |        }
  104|   227k|    }

txgraph.cpp:_Z11ClearShrinkINSt3__16vectorINS0_19__basic_node_handleINS0_11__tree_nodeIN12_GLOBAL__N_111TxGraphImpl9ChunkDataEPvEENS0_9allocatorIS6_EENS0_27__set_node_handle_specificsEEENS9_ISC_EEEEEvRT_:
   57|  1.24M|{
   58|       |    // There are various ways to clear a vector and release its memory:
   59|       |    //
   60|       |    // 1. V{}.swap(v)
   61|       |    // 2. v = V{}
   62|       |    // 3. v = {}; v.shrink_to_fit();
   63|       |    // 4. v.clear(); v.shrink_to_fit();
   64|       |    //
   65|       |    // (2) does not appear to release memory in glibc debug mode, even if v.shrink_to_fit()
   66|       |    // follows. (3) and (4) rely on std::vector::shrink_to_fit, which is only a non-binding
   67|       |    // request. Therefore, we use method (1).
   68|       |
   69|  1.24M|    V{}.swap(v);
   70|  1.24M|}

_ZN19WalletInitInterfaceD2Ev:
   25|      2|    virtual ~WalletInitInterface() = default;

