Skip to content

Use FD_DCHECK instead of FD_TMPL_USE_HANDHOLDING#10186

Open
ripatel-fd wants to merge 1 commit into
mainfrom
ripatel/tmpl-handholding
Open

Use FD_DCHECK instead of FD_TMPL_USE_HANDHOLDING#10186
ripatel-fd wants to merge 1 commit into
mainfrom
ripatel/tmpl-handholding

Conversation

@ripatel-fd

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings June 11, 2026 15:58
@github-actions

Copy link
Copy Markdown

Performance Measurements ⏳

Suite Baseline New Change
backtest mainnet-424669000-perf per slot 0.050336 s 0.050434 s 0.195%
backtest mainnet-424669000-perf snapshot load 1.957 s 1.959 s 0.102%
backtest mainnet-424669000-perf total elapsed 65.236013 s 65.362694 s 0.194%
firedancer mem usage with mainnet.toml 504.41 GiB 504.41 GiB 0.000%

This comment was marked as spam.

@greptile-jt

greptile-jt Bot commented Jun 11, 2026

Copy link
Copy Markdown

Greptile Summary

This PR migrates all src/util/tmpl/ template files from the FD_TMPL_USE_HANDHOLDING preprocessor guard pattern to the unified FD_DCHECK_CRIT macro system controlled by FD_DCHECK_STYLE. This consolidates two separate debug assertion mechanisms into one, reducing boilerplate and enabling three configuration modes: runtime assertions (FD_DCHECK_STYLE==1), production no-ops (==0), and compiler assumptions (==-1).

  • 32 files changed across 16 template implementations and 16 test files, removing ~220 lines of #if/#endif boilerplate
  • Most migrations are clean 1:1 replacements where #if FD_TMPL_USE_HANDHOLDING / if(...) FD_LOG_CRIT((...)) / #endif becomes a single FD_DCHECK_CRIT(...) call
  • Test files updated from FD_TMPL_USE_HANDHOLDING to FD_DCHECK_STYLE==1 guards, with new test coverage added for set range operations, pool release edge cases, and smallset range checks
  • Potential performance concern: In fd_map.c, fd_map_dynamic.c, and fd_map_chain.c, code blocks that were previously entirely inside #if FD_TMPL_USE_HANDHOLDING (probe chain validation loops, duplicate key queries) are now always compiled. While the FD_DCHECK_CRIT assertions become no-ops in production, the surrounding loop/call structure remains and may not be optimized away by the compiler
  • FD_TMPL_USE_HANDHOLDING is still used in fd_accdb.c and fd_gui_live_table_tmpl.c (not part of this PR)

Confidence Score: 3/5

Mostly mechanical refactor but three files have debug-only code blocks that now run unconditionally in production builds, which may cause performance regressions on hot paths.

The vast majority of changes (deque, set, pool, treap, heap, prq, dlist, slist, smallset) are clean 1:1 migrations with no behavioral change. However, fd_map.c, fd_map_dynamic.c, and fd_map_chain.c have code that was previously entirely compiled out in non-handholding builds but now always executes — specifically probe chain validation loops and duplicate key queries. These are on hot paths (map remove/insert) where unnecessary work matters.

src/util/tmpl/fd_map.c, src/util/tmpl/fd_map_dynamic.c, and src/util/tmpl/fd_map_chain.c need attention — their remove() and idx_insert() functions have debug-only code blocks that are no longer guarded.

Important Files Changed

Filename Overview
src/util/tmpl/fd_map.c Debug-only probe chain validation loop in remove() is now always compiled, potentially adding O(probe_length) overhead in production builds.
src/util/tmpl/fd_map_dynamic.c Same probe chain loop issue as fd_map.c — debug-only validation loop in remove() now always runs.
src/util/tmpl/fd_map_chain.c Duplicate key query in idx_insert() for non-multi maps now always executed (was debug-only). Iterator checks cleanly migrated.
src/util/tmpl/fd_deque.c Clean migration of all FD_TMPL_USE_HANDHOLDING guards to FD_DCHECK_CRIT. No structural code outside assertions.
src/util/tmpl/fd_deque_dynamic.c Clean migration, all checks were simple assertion guards with no surrounding debug-only code.
src/util/tmpl/fd_pool.c Clean migration. Uses && instead of
src/util/tmpl/fd_set.c Clean migration of all set operations. All checks are simple assertions with no surrounding debug-only code.
src/util/tmpl/fd_set_dynamic.c Minor behavioral change: hdr pointer computation in insert_range, remove_range, and range_cnt now always executed (was debug-only). Negligible overhead.
src/util/tmpl/fd_map_slot.c Changed from FD_TEST (always-on) to FD_DCHECK_CRIT (configurable). remove() checks properly migrated with correct MAP_(ele_is_free) syntax.
src/util/tmpl/fd_map_giant.c Simple, clean migration. Only two assertion sites, both straightforward.
src/util/tmpl/fd_treap.c Clean migration of bounds checks in idx_insert and idx_remove.
src/util/tmpl/fd_heap.c Clean migration of range check and empty check in idx_insert and idx_remove_min.
src/util/tmpl/fd_prq.c Clean migration. Minor improvement: new() uses && instead of
src/util/tmpl/fd_dlist.c Clean migration of empty-list checks in peek and pop operations.
src/util/tmpl/fd_slist.c Clean migration of empty-list checks in peek and pop operations.
src/util/tmpl/fd_smallset.c Clean migration. range() check was previously always guarded by FD_TMPL_USE_HANDHOLDING with a comment about unsigned comparison.
src/util/tmpl/test_pool.c Updated guard to FD_DCHECK_STYLE==1. Added new test coverage for idx_release with null index, sentinel, and already-released elements.
src/util/tmpl/test_set.c Updated guard. Added new test coverage for range operation handholding checks.
src/util/tmpl/test_set_dynamic.c Updated guard. Added new test coverage for range operation handholding checks.
src/util/tmpl/test_smallset.c Updated guard. Added set_range handholding test.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant map as fd_map remove()
    participant dcheck as FD_DCHECK_CRIT

    Note over Caller,dcheck: FD_DCHECK_STYLE==1 (Debug)
    Caller->>map: remove(map, entry)
    map->>dcheck: validate entry key
    dcheck-->>map: CRIT if invalid
    map->>map: walk probe chain (validation loop)
    map->>dcheck: validate each slot
    dcheck-->>map: CRIT if not found
    map->>map: perform actual removal

    Note over Caller,dcheck: FD_DCHECK_STYLE==0 (Production)
    Caller->>map: remove(map, entry)
    map->>dcheck: ((void)(c)) - no-op
    map->>map: walk probe chain (loop still runs!)
    map->>dcheck: ((void)(c)) - no-op
    map->>map: perform actual removal
    Note right of map: Extra O(probe_len) overhead
Loading

Reviews (1): Last reviewed commit: "Use FD_DCHECK instead of FD_TMPL_USE_HAN..." | Re-trigger Greptile

Comment thread src/util/tmpl/fd_map.c
Comment on lines 381 to 390
{
MAP_KEY_T _key = entry->MAP_KEY;
MAP_HASH_T _hash = MAP_(key_hash)( _key );
ulong _slot = MAP_(private_start)( _hash );
for(;;) {
if( FD_UNLIKELY( MAP_(key_inval)( map[_slot].MAP_KEY ) ) ) FD_LOG_CRIT(( "entry is not in the map" ));
FD_DCHECK_CRIT( !MAP_(key_inval)( map[_slot].MAP_KEY ), "entry is not in the map" );
if( FD_LIKELY( &map[_slot]==entry ) ) break;
_slot = MAP_(private_next)( _slot );
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Debug-only probe chain loop now runs unconditionally

This block was previously inside #if FD_TMPL_USE_HANDHOLDING and the comment on line 378 explicitly says "walks the probe chain a second time, acceptable for a debug-only check." After this refactor, the loop structure (variable declarations, for-loop with break, MAP_(private_next) calls) is always compiled and executed, even in production builds where FD_DCHECK_STYLE==0.

With FD_DCHECK_STYLE==0, FD_DCHECK_CRIT(c,m) becomes ((void)(c)), so the inner assertion is a no-op. However, the surrounding loop itself — which walks the probe chain until &map[_slot]==entry — is still executed. While a sufficiently aggressive optimizer might determine this is dead code (since _slot is unused after the block), this is not guaranteed for a loop with data-dependent bounds and memory reads.

This should be guarded with #if FD_DCHECK_STYLE==1 to preserve the original intent of being debug-only, or the entire block should be wrapped in an FD_DCHECK_ALERT-like pattern that compiles out entirely in production.

Comment on lines 437 to 447
{
ulong _slot_mask = hdr->slot_mask;
MAP_KEY_T _key = entry->MAP_KEY;
MAP_HASH_T _hash = MAP_(key_hash)( _key, hdr->seed );
ulong _slot = MAP_(private_start)( _hash, _slot_mask );
for(;;) {
if( FD_UNLIKELY( MAP_(key_inval)( map[_slot].MAP_KEY ) ) ) FD_LOG_CRIT(( "entry is not in the map" ));
FD_DCHECK_CRIT( !MAP_(key_inval)( map[_slot].MAP_KEY ), "entry is not in the map" );
if( FD_LIKELY( &map[_slot]==entry ) ) break;
_slot = MAP_(private_next)( _slot, _slot_mask );
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Same debug-only loop issue as fd_map.c

Same concern as in fd_map.c: the probe chain validation loop that was previously guarded by #if FD_TMPL_USE_HANDHOLDING is now always compiled. The comment on line 433 says "Acceptable for a debug-only check." With FD_DCHECK_STYLE==0, the FD_DCHECK_CRIT calls become no-ops but the loop itself still executes, adding O(probe_length) overhead to every remove() call in production builds.

This block should be guarded with #if FD_DCHECK_STYLE==1 to maintain the original intent.

Comment on lines +851 to 854
# if !MAP_MULTI
FD_DCHECK_CRIT( MAP_(private_idx_is_null)( MAP_(idx_query)( join, &pool[ ele_idx ].MAP_KEY, MAP_(private_idx_null)(), pool ) ),
"ele_idx already in map" );
# endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Debug-only duplicate key query now runs in production for non-multi maps

Previously this was guarded by #if FD_TMPL_USE_HANDHOLDING && !MAP_MULTI. Now the #if FD_TMPL_USE_HANDHOLDING guard is removed, leaving only #if !MAP_MULTI. With FD_DCHECK_STYLE==0, FD_DCHECK_CRIT(c,m) expands to ((void)(c)), which means MAP_(idx_query)(...) — a full hash chain traversal — is always evaluated in production for every insert on non-multi maps. The result is discarded, but the traversal still executes.

Whether the compiler can elide this call depends on whether MAP_(idx_query) is provably side-effect-free. To preserve the original intent, this should be:

Suggested change
# if !MAP_MULTI
FD_DCHECK_CRIT( MAP_(private_idx_is_null)( MAP_(idx_query)( join, &pool[ ele_idx ].MAP_KEY, MAP_(private_idx_null)(), pool ) ),
"ele_idx already in map" );
# endif
# if !MAP_MULTI && FD_DCHECK_STYLE
FD_DCHECK_CRIT( MAP_(private_idx_is_null)( MAP_(idx_query)( join, &pool[ ele_idx ].MAP_KEY, MAP_(private_idx_null)(), pool ) ),
"ele_idx already in map" );
# endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants