From 76b7318579c78b4e61c397fe53ac593c37c2f8e3 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:36 -0400 Subject: [PATCH 01/47] Fix O(N) memory explosion in scoring path for large candidate pools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background: the async path streams candidates from a child process into a two-level cands_top table and scores them on a persistent background scoring thread using a pool of parallel worker pthreads. When 50+ million candidates are in the pool, two allocations inside scoring_thread_fn previously scaled linearly with pool size: 1. char **snap = malloc(scount * sizeof *snap) — a flat snapshot of all candidate pointers. At 55.9 M candidates this is 447 MB allocated in a single malloc call, causing VM pressure stalls lasting 17+ seconds. 2. struct AsyncScoringBatch *batches — each batch embedded xs[BATCH_SIZE] (2048 × 16 B = 32 KB) inline. The array grew to 32768 entries via doubling, reaching 32768 × 32 KB = 1 GB before any worker ran. Both allocations existed since the original batch-parallel design and survived the chunked cands_top fix (commit 9883b51, which only addressed the reader's realloc stall). A 17-second gap in the C-side debug log between the last reader allocation and the first post-reader scoring event confirmed the scoring thread was the culprit. Fix: replace the batch/snapshot design with a range-based worker design. Workers no longer receive pre-filled string arrays. Instead: - AsyncScoringRange { size_t from; size_t to; } (16 bytes each) replaces AsyncScoringBatch (32 KB each). The range array for 55 M candidates at BATCH_SIZE=2048 is ~430 KB rather than 1 GB. - Workers resolve candidate pointers on the fly from cands_top via the shift+mask accessor (gi >> CANDS_BLOCK_SHIFT, gi & CANDS_BLOCK_MASK). This is safe because entries at index i < pool_count are immutably written by the reader and never moved; the scoring thread captures pool_count under s->mu before spawning workers. - Per-worker result buffers grow only as matches are found, bounded by ceil(limit / num_workers + 1) × 4. For 10000-candidate limit across 9 workers, each worker buffer is ≤ 71 KB; total flat memory after merge is ≤ 640 KB regardless of pool size. - The char **snap intermediate copy is eliminated entirely; workers read directly from cands_top. Memory footprint for 55 M candidates (limit=10000, 9 workers): Allocation Old New snap 447 MB 0 (removed) batches array ~1 GB ~430 KB per-worker results N/A ≤71 KB each flat after merge same ≤640 KB Also adds a 20-entry LRU result cache (SharedIdx, ref-counted) that maps (filter, pool_gen) to a scored snapshot. Exact cache hits return without scheduling a new scoring run. Prefix cache hits restrict the next scoring run to the cached entry's matched indices plus newly-arrived candidates (delta refinement), avoiding a full re-score of the entire pool on each keystroke during incremental narrowing (e.g. "f" → "fo" → "foo"). --- fzf-native-ctest.c | 456 ++++++++++++++++++++++++++- fzf-native-module.c | 749 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 1066 insertions(+), 139 deletions(-) diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index 3983b8c..728eb48 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -158,7 +158,7 @@ static void test_matches_qsort(void) { /* Abuse the str pointer as an order tag; counting_sort_scored never dereferences str, only copies it, so this is safe for tests. */ static ScoredStr make_scored(int score, size_t tag) { - ScoredStr s; + ScoredStr s = {0}; s.str = (char *)(uintptr_t)tag; s.score = score; return s; @@ -262,24 +262,31 @@ static void test_strip_ansi_bare_esc(void) { * async_reader (pipe-based; no Emacs runtime needed) * ===================================================================== */ +/* `cap` is no longer meaningful — chunked storage allocates blocks on + demand. Argument retained so existing callers don't change. */ static AsyncSession *make_async_session(FILE *fp, size_t cap) { + (void)cap; AsyncSession *s = calloc(1, sizeof *s); if (!s) return NULL; - s->fp = fp; - s->cap = cap; - s->cands = calloc(cap, sizeof *s->cands); - if (!s->cands) { free(s); return NULL; } + s->fp = fp; pthread_mutex_init(&s->mu, NULL); return s; } static void free_async_session(AsyncSession *s) { arena_free(&s->arena); - free(s->cands); + for (size_t i = 0; i < CANDS_TOP_CAP; i++) { + if (s->cands_top[i]) free(s->cands_top[i]); + } pthread_mutex_destroy(&s->mu); free(s); } +/* Test helper: read candidate i via the chunked accessor. */ +static char *cands_at(AsyncSession *s, size_t i) { + return s->cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK]; +} + static void test_async_reader_basic(void) { int pfd[2]; CHECK(pipe(pfd) == 0); @@ -296,9 +303,9 @@ static void test_async_reader_basic(void) { async_reader((void *)s); CHECK(s->count == 3); - CHECK(strcmp(s->cands[0], "alpha") == 0); - CHECK(strcmp(s->cands[1], "beta") == 0); - CHECK(strcmp(s->cands[2], "gamma") == 0); + CHECK(strcmp(cands_at(s, 0), "alpha") == 0); + CHECK(strcmp(cands_at(s, 1), "beta") == 0); + CHECK(strcmp(cands_at(s, 2), "gamma") == 0); free_async_session(s); } @@ -319,13 +326,14 @@ static void test_async_reader_ansi_stripping(void) { async_reader((void *)s); CHECK(s->count == 2); - CHECK(strcmp(s->cands[0], "file.txt") == 0); - CHECK(strcmp(s->cands[1], "plain.c") == 0); + CHECK(strcmp(cands_at(s, 0), "file.txt") == 0); + CHECK(strcmp(cands_at(s, 1), "plain.c") == 0); free_async_session(s); } static void test_async_reader_buffer_growth(void) { - /* Initial cap=4, write 32 lines — exercises the realloc doubling path. */ + /* Write a small batch of lines and verify they round-trip through the + chunked cands_top storage in order. All 32 fit within block 0. */ enum { NLINES = 32 }; int pfd[2]; CHECK(pipe(pfd) == 0); @@ -345,11 +353,402 @@ static void test_async_reader_buffer_growth(void) { char expected[32]; for (int i = 0; i < NLINES; i++) { snprintf(expected, sizeof expected, "line%d", i); - CHECK(strcmp(s->cands[i], expected) == 0); + CHECK(strcmp(cands_at(s, i), expected) == 0); } free_async_session(s); } +/* ===================================================================== + * Cache (per-session result cache, phase 1: exact-match) + * ===================================================================== + * The cache stores ScoredStr arrays whose .str pointers normally point + * into AsyncSession.arena. In these tests we use string literals as + * the .str values; the cache only memcpys the pointers, so this is + * safe — we never dereference them after teardown. + */ + +static ScoredStr make_top(const char *str, int score) { + ScoredStr s = {0}; + s.str = (char *)str; /* not freed by the cache */ + s.score = score; + return s; +} + +static void test_cache_lookup_miss_on_empty(void) { + Cache c; + cache_init(&c); + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "foo", &out_top, &out_count, + &out_sidx, &out_gen) == false); + CHECK(out_top == NULL); + cache_destroy(&c); +} + +static void test_cache_insert_then_lookup_hit(void) { + Cache c; + cache_init(&c); + ScoredStr top[2] = { make_top("alpha", 42), make_top("beta", 17) }; + + cache_insert(&c, "fo", 1000, top, 2, NULL, 0); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "fo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_count == 2); + CHECK(out_gen == 1000); + CHECK(out != NULL); + CHECK(out[0].score == 42); + CHECK(strcmp(out[0].str, "alpha") == 0); + CHECK(out[1].score == 17); + CHECK(strcmp(out[1].str, "beta") == 0); + free(out); + cache_destroy(&c); +} + +static void test_cache_lookup_miss_distinct_query(void) { + Cache c; + cache_init(&c); + ScoredStr top[1] = { make_top("alpha", 42) }; + cache_insert(&c, "fo", 100, top, 1, NULL, 0); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "bar", &out, &out_count, &out_sidx, &out_gen) == false); + CHECK(out == NULL); + cache_destroy(&c); +} + +static void test_cache_insert_updates_in_place(void) { + /* Re-inserting the same query overwrites the existing entry rather + than creating a duplicate. Verify count stays at 1 and the new + data wins. */ + Cache c; + cache_init(&c); + ScoredStr v1[1] = { make_top("alpha", 10) }; + ScoredStr v2[2] = { make_top("alpha", 99), make_top("beta", 50) }; + + cache_insert(&c, "fo", 100, v1, 1, NULL, 0); + cache_insert(&c, "fo", 200, v2, 2, NULL, 0); + CHECK(c.count == 1); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "fo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_count == 2); + CHECK(out_gen == 200); + CHECK(out[0].score == 99); + CHECK(out[1].score == 50); + free(out); + cache_destroy(&c); +} + +static void test_cache_lru_eviction_at_capacity(void) { + /* Fill the cache, insert one more, verify the oldest entry is gone + and all others remain. */ + Cache c; + cache_init(&c); + ScoredStr one[1] = { make_top("x", 1) }; + + char qbuf[16]; + for (size_t i = 0; i < ASYNC_CACHE_MAX_ENTRIES; i++) { + snprintf(qbuf, sizeof qbuf, "q%zu", i); + cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); + } + CHECK(c.count == ASYNC_CACHE_MAX_ENTRIES); + + /* Insert one more — should evict q0 (the LRU tail). */ + cache_insert(&c, "extra", 999, one, 1, NULL, 0); + CHECK(c.count == ASYNC_CACHE_MAX_ENTRIES); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + + /* q0 is gone. */ + CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == false); + + /* q1 .. q(N-1) are still present. */ + for (size_t i = 1; i < ASYNC_CACHE_MAX_ENTRIES; i++) { + snprintf(qbuf, sizeof qbuf, "q%zu", i); + out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; + CHECK(cache_lookup_exact(&c, qbuf, &out, &out_count, &out_sidx, &out_gen) == true); + free(out); + } + + /* And the freshly inserted "extra" is present. */ + out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; + CHECK(cache_lookup_exact(&c, "extra", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_gen == 999); + free(out); + + cache_destroy(&c); +} + +static void test_cache_touch_on_hit(void) { + /* Fill the cache; touch q0 (the oldest) so it becomes MRU; insert + one more; verify q0 survived and q1 (now the LRU) was evicted. */ + Cache c; + cache_init(&c); + ScoredStr one[1] = { make_top("x", 1) }; + + char qbuf[16]; + for (size_t i = 0; i < ASYNC_CACHE_MAX_ENTRIES; i++) { + snprintf(qbuf, sizeof qbuf, "q%zu", i); + cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); + } + + /* Touch q0 — moves it to head (MRU). */ + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == true); + free(out); + + /* Now the LRU tail is q1. Insert one more; q1 should be evicted. */ + cache_insert(&c, "extra", 999, one, 1, NULL, 0); + + out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; + CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == true); + free(out); + + out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; + CHECK(cache_lookup_exact(&c, "q1", &out, &out_count, &out_sidx, &out_gen) == false); + + cache_destroy(&c); +} + +static void test_cache_insert_zero_count(void) { + /* Empty top[] is a legitimate "no matches" cache entry; verify it + stores and looks up cleanly. */ + Cache c; + cache_init(&c); + cache_insert(&c, "nothing", 500, NULL, 0, NULL, 0); + + ScoredStr *out = (ScoredStr *)0xdeadbeef; + SharedIdx *out_sidx = NULL; + size_t out_count = 99, out_gen = 0; + CHECK(cache_lookup_exact(&c, "nothing", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out == NULL); + CHECK(out_count == 0); + CHECK(out_gen == 500); + cache_destroy(&c); +} + +/* ===================================================================== + * Phase 2: subsumes() + cache_lookup_prefix() + * ===================================================================== */ + +static void test_subsumes_extending_term(void) { + /* "fo" → "foo" — extending a single term. */ + CHECK(subsumes("fo", "foo") == true); + CHECK(subsumes("foo", "fo") == false); /* shorter doesn't subsume longer's superset */ +} + +static void test_subsumes_adding_and_term(void) { + /* "fo" → "fo bar" — adding an AND term. Q starts with Q' textually. */ + CHECK(subsumes("fo", "fo bar") == true); +} + +static void test_subsumes_adding_negation(void) { + /* "fo" → "fo !bad" — adding a negation narrows. */ + CHECK(subsumes("fo", "fo !bad") == true); +} + +static void test_subsumes_or_in_prefix_rejected(void) { + /* Q' contains '|' → not safe to refine from. */ + CHECK(subsumes("fo|bar", "fo|bar baz") == false); +} + +static void test_subsumes_or_in_query_rejected(void) { + /* Q contains '|' → introduces OR; not safe to assume narrowing. */ + CHECK(subsumes("fo", "fo|bar") == false); +} + +static void test_subsumes_empty_prefix(void) { + /* Empty Q' subsumes anything (in practice we never cache empty). */ + CHECK(subsumes("", "anything") == true); +} + +static void test_subsumes_non_textual_prefix(void) { + /* "ba" is not a textual prefix of "foo". */ + CHECK(subsumes("ba", "foo") == false); +} + +static void test_subsumes_equal_strings(void) { + /* Q' == Q is trivially a prefix; subsumes() returns true. Callers + who want to skip exact matches do so explicitly. */ + CHECK(subsumes("fo", "fo") == true); +} + +static void test_cache_lookup_prefix_finds_subsumer(void) { + Cache c; + cache_init(&c); + ScoredStr top1[1] = { make_top("alpha", 10) }; + uint32_t midx[3] = { 0, 5, 9 }; + cache_insert(&c, "fo", 100, top1, 1, midx, 3); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "foo", + &out_top, &out_count, + &out_sidx, &out_gen) == true); + CHECK(out_count == 1); + CHECK(out_sidx != NULL); + CHECK(out_sidx->count == 3); + CHECK(out_gen == 100); + CHECK(out_sidx->idx[0] == 0 && out_sidx->idx[1] == 5 && out_sidx->idx[2] == 9); + free(out_top); + shared_idx_release(out_sidx); + cache_destroy(&c); +} + +static void test_cache_lookup_prefix_picks_longest(void) { + /* Two subsumers: "f" and "fo". Looking up "foo" should return "fo" + (the longer one, since longer prefixes are more selective). */ + Cache c; + cache_init(&c); + ScoredStr top1[1] = { make_top("x", 1) }; + uint32_t midx_f[10]; for (int i = 0; i < 10; i++) midx_f[i] = (uint32_t)i; + uint32_t midx_fo[5]; for (int i = 0; i < 5; i++) midx_fo[i] = (uint32_t)i; + + cache_insert(&c, "f", 50, top1, 1, midx_f, 10); + cache_insert(&c, "fo", 100, top1, 1, midx_fo, 5); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "foo", + &out_top, &out_count, + &out_sidx, &out_gen) == true); + CHECK(out_sidx != NULL); + CHECK(out_sidx->count == 5); /* came from "fo" entry, not "f" */ + CHECK(out_gen == 100); + free(out_top); + shared_idx_release(out_sidx); + cache_destroy(&c); +} + +static void test_cache_lookup_prefix_skips_or_query(void) { + Cache c; + cache_init(&c); + ScoredStr top[1] = { make_top("x", 1) }; + uint32_t midx[1] = { 0 }; + cache_insert(&c, "fo", 100, top, 1, midx, 1); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + /* Q has '|' — refinement isn't safe. */ + CHECK(cache_lookup_prefix(&c, "fo|bar", + &out_top, &out_count, + &out_sidx, &out_gen) == false); + cache_destroy(&c); +} + +static void test_cache_lookup_prefix_skips_exact(void) { + /* If only an exact-match entry exists (no actual subsuming prefix), + cache_lookup_prefix returns false — exact matches are the + caller's responsibility (cache_lookup_exact). */ + Cache c; + cache_init(&c); + ScoredStr top[1] = { make_top("x", 1) }; + uint32_t midx[1] = { 0 }; + cache_insert(&c, "fo", 100, top, 1, midx, 1); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "fo", + &out_top, &out_count, + &out_sidx, &out_gen) == false); + cache_destroy(&c); +} + +static void test_cache_lookup_prefix_miss_when_no_subsumer(void) { + Cache c; + cache_init(&c); + ScoredStr top[1] = { make_top("x", 1) }; + uint32_t midx[1] = { 0 }; + cache_insert(&c, "ba", 100, top, 1, midx, 1); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "foo", + &out_top, &out_count, + &out_sidx, &out_gen) == false); + cache_destroy(&c); +} + +static void test_cache_insert_with_matched_idx(void) { + /* Verify cache_insert + cache_lookup_exact round-trip the matched_idx + array correctly. */ + Cache c; + cache_init(&c); + ScoredStr top[2] = { make_top("alpha", 30), make_top("beta", 20) }; + uint32_t midx[4] = { 7, 13, 21, 100 }; + cache_insert(&c, "fo", 200, top, 2, midx, 4); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "fo", + &out_top, &out_count, + &out_sidx, &out_gen) == true); + CHECK(out_sidx != NULL); + CHECK(out_sidx->count == 4); + CHECK(out_sidx->idx[0] == 7); + CHECK(out_sidx->idx[1] == 13); + CHECK(out_sidx->idx[2] == 21); + CHECK(out_sidx->idx[3] == 100); + free(out_top); + shared_idx_release(out_sidx); + cache_destroy(&c); +} + +static void test_shared_idx_refcount_roundtrip(void) { + /* alloc → retain → release (first) → release (last) → freed. */ + uint32_t src[3] = { 10, 20, 30 }; + SharedIdx *p = shared_idx_alloc(src, 3); + CHECK(p != NULL); + CHECK(p->count == 3); + CHECK(p->idx[0] == 10 && p->idx[1] == 20 && p->idx[2] == 30); + + SharedIdx *q = shared_idx_retain(p); + CHECK(q == p); + + shared_idx_release(p); /* refcount 2 → 1; still live */ + CHECK(q->count == 3); /* accessible via q */ + shared_idx_release(q); /* refcount 1 → 0; freed */ +} + +static void test_cache_insert_or_query_no_idx(void) { + /* OR queries (containing '|') must never store matched_idx since they + can never serve as prefix-refinement sources. */ + Cache c; + cache_init(&c); + ScoredStr top[1] = { make_top("y", 5) }; + uint32_t midx[3] = { 1, 2, 3 }; + cache_insert(&c, "foo | bar", 100, top, 1, midx, 3); + + ScoredStr *out_top = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_exact(&c, "foo | bar", + &out_top, &out_count, + &out_sidx, &out_gen) == true); + CHECK(out_count == 1); + CHECK(out_sidx == NULL); /* OR query: no matched_idx stored */ + free(out_top); + cache_destroy(&c); +} + /* ================================================================= */ int main(void) { @@ -381,6 +780,37 @@ int main(void) { RUN(test_async_reader_ansi_stripping); RUN(test_async_reader_buffer_growth); + printf("--- cache ---\n"); + RUN(test_cache_lookup_miss_on_empty); + RUN(test_cache_insert_then_lookup_hit); + RUN(test_cache_lookup_miss_distinct_query); + RUN(test_cache_insert_updates_in_place); + RUN(test_cache_lru_eviction_at_capacity); + RUN(test_cache_touch_on_hit); + RUN(test_cache_insert_zero_count); + + printf("--- subsumes ---\n"); + RUN(test_subsumes_extending_term); + RUN(test_subsumes_adding_and_term); + RUN(test_subsumes_adding_negation); + RUN(test_subsumes_or_in_prefix_rejected); + RUN(test_subsumes_or_in_query_rejected); + RUN(test_subsumes_empty_prefix); + RUN(test_subsumes_non_textual_prefix); + RUN(test_subsumes_equal_strings); + + printf("--- shared_idx ---\n"); + RUN(test_shared_idx_refcount_roundtrip); + + printf("--- cache_lookup_prefix ---\n"); + RUN(test_cache_lookup_prefix_finds_subsumer); + RUN(test_cache_lookup_prefix_picks_longest); + RUN(test_cache_lookup_prefix_skips_or_query); + RUN(test_cache_lookup_prefix_skips_exact); + RUN(test_cache_lookup_prefix_miss_when_no_subsumer); + RUN(test_cache_insert_with_matched_idx); + RUN(test_cache_insert_or_query_no_idx); + if (failed == 0) { printf("\nAll tests passed.\n"); return 0; diff --git a/fzf-native-module.c b/fzf-native-module.c index b1aee3b..e2d4189 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -604,10 +604,27 @@ emacs_value fzf_native_make_slab(emacs_env *env, #if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) -#define ASYNC_INIT_CAP 4096 #define ASYNC_LINE_MAX 8192 #define ARENA_CHUNK_SIZE (4 * 1024 * 1024) /* 4 MB per chunk */ +/* Chunked candidate-pointer storage. + * + * The candidate-pointer array is split into fixed-size blocks owned by a + * top-level pointer table. Reader appends to the current block; when a + * block fills, it allocates the next one. No realloc ever moves pointer + * data, so the worst-case allocation the reader performs is a single + * block — predictable cost regardless of pool size. + * + * cands_top[] : CANDS_TOP_CAP slots × 8 B (~32 KB, fixed) + * cands_top[i] : CANDS_BLOCK_SIZE × 8 B (2 MB, on demand) + * + * Defaults: 256 K pointers per block, 4096 blocks → 1 G candidates max. + */ +#define CANDS_BLOCK_SHIFT 18 +#define CANDS_BLOCK_SIZE ((size_t)1 << CANDS_BLOCK_SHIFT) +#define CANDS_BLOCK_MASK (CANDS_BLOCK_SIZE - 1) +#define CANDS_TOP_CAP 4096 + /* Arena allocator: strings are packed into large chunks so freeing the entire candidate set is O(chunks) instead of O(candidates). */ typedef struct ArenaChunk { struct ArenaChunk *next; size_t used; char data[]; } ArenaChunk; @@ -649,7 +666,282 @@ static size_t async_strip_ansi(char *s, size_t len) { return w; } -typedef struct { char *str; int score; } ScoredStr; +/* idx is the candidate's position in the scoring-time snapshot. For + full-pool scoring this is the index in s->cands. For refinement + scoring (Phase 2) it is the index in s->cands of the candidate that + was looked up via the prefix entry's matched_idx, or in the delta + range. Same struct size as before on 64-bit (8 + 4 + 4 = 16). */ +typedef struct { char *str; int score; uint32_t idx; } ScoredStr; + +/* ===================================================================== + * Result cache (phase 1: exact-match, stale-while-revalidate) + * ===================================================================== + * Per-session LRU keyed by query string. Each entry holds a copy of + * the top-K ScoredStr published when the query was last scored, plus + * the candidate-pool size (s->count) at that moment. When dispatch + * sees an exact hit at the current pool size, it can return the + * cached result without scheduling new scoring. An older pool size + * means new candidates have arrived since: still return the cached + * result, but schedule a re-score so the next dispatch picks up the + * fresh data. + * + * ScoredStr.str points into AsyncSession.arena, which outlives every + * cache entry, so the cache stores those pointers directly without + * copying string bytes. Only the top[] array and the query string + * itself are owned by the cache. + * + * Reads happen at dispatch (Emacs main thread); writes happen at + * scoring-thread publish. Both serialize through `cache->mu`. + */ + +#define ASYNC_CACHE_MAX_ENTRIES 20 +/* Reference-counted immutable index array. Allocated once, retained by each + consumer in O(1) (atomic refcount bump under the cache mutex — no memcpy), + and freed when the last consumer releases it. Eliminates the multi-hundred- + millisecond dup_idx() stall that previously blocked the Emacs main thread + for large match sets (while-no-input cannot interrupt a blocking C call). */ +typedef struct { + _Atomic uint32_t refcount; + size_t count; + uint32_t idx[]; /* flexible array */ +} SharedIdx; + +static SharedIdx *shared_idx_alloc(const uint32_t *src, size_t n) { + if (!n || !src) return NULL; + SharedIdx *p = malloc(sizeof *p + n * sizeof *p->idx); + if (!p) return NULL; + atomic_init(&p->refcount, 1); + p->count = n; + memcpy(p->idx, src, n * sizeof *p->idx); + return p; +} +static SharedIdx *shared_idx_retain(SharedIdx *p) { + if (p) atomic_fetch_add_explicit(&p->refcount, 1, memory_order_relaxed); + return p; +} +static void shared_idx_release(SharedIdx *p) { + if (p && atomic_fetch_sub_explicit(&p->refcount, 1, memory_order_acq_rel) == 1) + free(p); +} + +typedef struct CacheEntry { + struct CacheEntry *prev; /* toward head (MRU); NULL at head */ + struct CacheEntry *next; /* toward tail (LRU); NULL at tail */ + char *query; /* strdup'd, owned */ + size_t pool_gen; /* s->count at score time */ + ScoredStr *top; /* malloc'd copy of published top-K */ + size_t top_count; + /* Phase 2: full match set (indices into s->cands at score time). + Reference-counted SharedIdx — retained without copying at lookup. */ + SharedIdx *matched_idx; +} CacheEntry; + +typedef struct { + pthread_mutex_t mu; + CacheEntry *head; /* most recently used */ + CacheEntry *tail; /* least recently used */ + size_t count; +} Cache; + +static void cache_init(Cache *c) { + pthread_mutex_init(&c->mu, NULL); + c->head = c->tail = NULL; + c->count = 0; +} + +static void cache_free_entry(CacheEntry *e) { + if (!e) return; + free(e->query); + free(e->top); + shared_idx_release(e->matched_idx); + free(e); +} + +static void cache_destroy(Cache *c) { + CacheEntry *e = c->head; + while (e) { CacheEntry *n = e->next; cache_free_entry(e); e = n; } + c->head = c->tail = NULL; + c->count = 0; + pthread_mutex_destroy(&c->mu); +} + +/* Detach `e` from the list. Caller holds c->mu. */ +static void cache_unlink(Cache *c, CacheEntry *e) { + if (e->prev) e->prev->next = e->next; else c->head = e->next; + if (e->next) e->next->prev = e->prev; else c->tail = e->prev; + e->prev = e->next = NULL; +} + +/* Push `e` to the head (MRU). Caller holds c->mu. */ +static void cache_push_head(Cache *c, CacheEntry *e) { + e->prev = NULL; + e->next = c->head; + if (c->head) c->head->prev = e; + c->head = e; + if (!c->tail) c->tail = e; +} + +/* Helper: malloc a copy of a ScoredStr array. Returns NULL when n=0 + (caller treats as "no top-K"). On alloc failure returns NULL and + the caller decides whether that is fatal (it usually isn't — the + cache is best-effort). */ +static ScoredStr *dup_scored(const ScoredStr *src, size_t n) { + if (!n) return NULL; + ScoredStr *out = malloc(n * sizeof *out); + if (out && src) memcpy(out, src, n * sizeof *out); + return out; +} + +/* Find an entry by query and bump it to head. Caller holds c->mu. */ +static CacheEntry *cache_lookup_locked(Cache *c, const char *query) { + for (CacheEntry *e = c->head; e; e = e->next) { + if (strcmp(e->query, query) == 0) { + if (e != c->head) { + cache_unlink(c, e); + cache_push_head(c, e); + } + return e; + } + } + return NULL; +} + +/* Exact-match lookup. On hit, bumps LRU, copies the cached top[] out and + retains (O(1) refcount bump) the SharedIdx so the caller can release the + cache mutex before working with the data. Returns true on hit, false on + miss or top alloc failure (treated the same as a miss). */ +static bool cache_lookup_exact(Cache *c, const char *query, + ScoredStr **out_top, size_t *out_top_count, + SharedIdx **out_sidx, + size_t *out_pool_gen) { + pthread_mutex_lock(&c->mu); + CacheEntry *e = cache_lookup_locked(c, query); + if (!e) { pthread_mutex_unlock(&c->mu); return false; } + + ScoredStr *top_copy = dup_scored(e->top, e->top_count); + if (e->top_count && !top_copy) { pthread_mutex_unlock(&c->mu); return false; } + + /* O(1): just bump the refcount while holding the mutex. */ + SharedIdx *sidx = shared_idx_retain(e->matched_idx); + + *out_top = top_copy; + *out_top_count = e->top_count; + *out_sidx = sidx; + *out_pool_gen = e->pool_gen; + pthread_mutex_unlock(&c->mu); + return true; +} + +/* Subsumption rule (Phase 2 v1, conservative): + Q' subsumes Q (matches(Q) ⊆ matches(Q')) iff + - neither Q nor Q' contains '|' (no OR clauses), and + - Q starts with Q' as a textual prefix. + This catches: extending a term ("fo" → "foo"), adding new AND + terms ("fo" → "fo bar"), adding negations ("fo" → "fo !x"), and + anchors. It rejects every OR query — those fall through to + full-pool scoring. */ +static bool subsumes(const char *q_prime, const char *q) { + if (strchr(q_prime, '|') || strchr(q, '|')) return false; + size_t lp = strlen(q_prime); + if (lp == 0) return true; + size_t lq = strlen(q); + if (lq < lp) return false; + return memcmp(q, q_prime, lp) == 0; +} + +/* Walk the cache for the longest Q' (other than Q itself) that subsumes Q. + On hit, bumps the chosen entry to MRU, copies its top[] out and retains + (O(1)) the SharedIdx. Returns true on hit, false otherwise (miss or + top alloc failure). */ +static bool cache_lookup_prefix(Cache *c, const char *query, + ScoredStr **out_top, size_t *out_top_count, + SharedIdx **out_sidx, + size_t *out_pool_gen) { + pthread_mutex_lock(&c->mu); + CacheEntry *best = NULL; + size_t best_len = 0; + for (CacheEntry *e = c->head; e; e = e->next) { + if (strcmp(e->query, query) == 0) continue; /* skip exact */ + if (!subsumes(e->query, query)) continue; + size_t len = strlen(e->query); + if (len > best_len) { best = e; best_len = len; } + } + if (!best) { pthread_mutex_unlock(&c->mu); return false; } + + ScoredStr *top_copy = dup_scored(best->top, best->top_count); + if (best->top_count && !top_copy) { pthread_mutex_unlock(&c->mu); return false; } + + /* O(1): just bump the refcount while holding the mutex. */ + SharedIdx *sidx = shared_idx_retain(best->matched_idx); + + if (best != c->head) { cache_unlink(c, best); cache_push_head(c, best); } + + *out_top = top_copy; + *out_top_count = best->top_count; + *out_sidx = sidx; + *out_pool_gen = best->pool_gen; + pthread_mutex_unlock(&c->mu); + return true; +} + +/* Insert (or update) an entry for `query`. All allocations are done OUTSIDE + the mutex so the critical section is O(1) pointer swaps only — no memcpy + of potentially-huge index arrays while the Emacs main thread waits. + matched_idx is stored only for non-OR queries (OR queries can never be + prefix-refinement sources). Cache is best-effort: no-op on alloc failure. + Evicted entries are freed after the mutex is released. */ +static void cache_insert(Cache *c, const char *query, size_t pool_gen, + const ScoredStr *top, size_t top_count, + const uint32_t *matched_idx, size_t match_count) { + bool store_idx = (matched_idx && match_count && !strchr(query, '|')); + + /* Pre-allocate ALL resources OUTSIDE the mutex. */ + ScoredStr *new_top = dup_scored(top, top_count); + SharedIdx *new_sidx = store_idx ? shared_idx_alloc(matched_idx, match_count) : NULL; + CacheEntry *fresh = calloc(1, sizeof *fresh); + char *qdup = fresh ? strdup(query) : NULL; + if ((top_count && !new_top) || (fresh && !qdup)) { + free(new_top); shared_idx_release(new_sidx); free(fresh); free(qdup); return; + } + + pthread_mutex_lock(&c->mu); + + CacheEntry *existing = cache_lookup_locked(c, query); + if (existing) { + ScoredStr *old_top = existing->top; + SharedIdx *old_sidx = existing->matched_idx; + existing->top = new_top; + existing->top_count = new_top ? top_count : 0; + existing->matched_idx = new_sidx; + existing->pool_gen = pool_gen; + pthread_mutex_unlock(&c->mu); + free(old_top); shared_idx_release(old_sidx); + free(qdup); free(fresh); + return; + } + + if (!fresh) { + pthread_mutex_unlock(&c->mu); + free(new_top); shared_idx_release(new_sidx); + return; + } + fresh->query = qdup; + fresh->top = new_top; + fresh->top_count = new_top ? top_count : 0; + fresh->matched_idx = new_sidx; + fresh->pool_gen = pool_gen; + + CacheEntry *evicted = NULL; + if (c->count >= ASYNC_CACHE_MAX_ENTRIES) { + evicted = c->tail; + if (evicted) { cache_unlink(c, evicted); c->count--; } + } + cache_push_head(c, fresh); + c->count++; + pthread_mutex_unlock(&c->mu); + + if (evicted) cache_free_entry(evicted); +} typedef struct { pthread_t reader; @@ -658,10 +950,9 @@ typedef struct { _Atomic bool stop; pthread_mutex_t mu; - Arena arena; /* backing storage for all candidate strings */ - char **cands; - size_t count; - size_t cap; + Arena arena; /* backing storage for all candidate strings */ + char **cands_top[CANDS_TOP_CAP]; /* chunked pointer array; entries are 2 MB blocks allocated on demand */ + _Atomic size_t count; /* written by reader (under mu); read by any thread */ _Atomic int gen; size_t last_filtered; /* candidates matching last filter */ @@ -679,9 +970,19 @@ typedef struct { char *score_current_filter; /* filter being actively scored (under score_req_mu) */ size_t score_current_limit; + /* Phase 2 refinement: when set, scoring scores against + score_req_refine_idx ∪ s->cands[score_req_refine_delta_from..) + instead of the full pool. refine_idx is a SharedIdx retained at + dispatch and released when the scoring thread steals it. */ + SharedIdx *score_req_refine_idx; /* may be NULL; ref-counted */ + size_t score_req_refine_delta_from; + bool score_req_has_refine; + pthread_mutex_t score_res_mu; ScoredStr *score_results; /* latest scored+sorted results */ size_t score_count; /* number of entries in score_results */ + + Cache cache; /* per-session result cache */ } AsyncSession; static void *async_reader(void *arg) { @@ -698,21 +999,35 @@ static void *async_reader(void *arg) { char *dup = arena_strdup(&s->arena, line, len); if (!dup) continue; - pthread_mutex_lock(&s->mu); - if (s->count >= s->cap) { - size_t ncap = s->cap * 2; - fzf_log("async_reader: reallocating candidates %zu -> %zu\n", s->cap, ncap); - char **nc = realloc(s->cands, ncap * sizeof *nc); - if (!nc) { free(dup); pthread_mutex_unlock(&s->mu); continue; } - s->cands = nc; - s->cap = ncap; + /* Chunked append: figure out which block this index lands in, allocate + the block on first use, write the slot, publish via the count + store-release. The largest single allocation we ever do here is + one block (CANDS_BLOCK_SIZE * 8 B = 2 MB) — predictable cost + regardless of pool size, so memory-pressure stalls scale O(1). */ + size_t cur = atomic_load_explicit(&s->count, memory_order_relaxed); + size_t hi = cur >> CANDS_BLOCK_SHIFT; + size_t lo = cur & CANDS_BLOCK_MASK; + if (hi >= CANDS_TOP_CAP) continue; /* 1 G-candidate ceiling */ + + if (s->cands_top[hi] == NULL) { + char **blk = malloc(CANDS_BLOCK_SIZE * sizeof *blk); + if (!blk) continue; + fzf_log("async_reader: allocated cands block %zu (%zu entries, %zu MB)\n", + hi, (size_t)CANDS_BLOCK_SIZE, + (size_t)(CANDS_BLOCK_SIZE * sizeof *blk) / (1024 * 1024)); + pthread_mutex_lock(&s->mu); + s->cands_top[hi] = blk; + pthread_mutex_unlock(&s->mu); } - s->cands[s->count++] = dup; + pthread_mutex_lock(&s->mu); + s->cands_top[hi][lo] = dup; + atomic_store_explicit(&s->count, cur + 1, memory_order_release); pthread_mutex_unlock(&s->mu); atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); } fzf_log("async_reader EXIT: total=%zu gen=%d\n", - s->count, (int)atomic_load_explicit(&s->gen, memory_order_relaxed)); + (size_t)atomic_load_explicit(&s->count, memory_order_relaxed), + (int)atomic_load_explicit(&s->gen, memory_order_relaxed)); return NULL; } @@ -721,7 +1036,8 @@ static void *scoring_thread_fn(void *arg); /* defined after async_scoring_worke static void async_session_destroy(void *ptr) { AsyncSession *s = ptr; if (!s) return; - fzf_log("async_session_destroy: pid=%d count=%zu\n", (int)s->pid, s->count); + fzf_log("async_session_destroy: pid=%d count=%zu\n", (int)s->pid, + (size_t)atomic_load_explicit(&s->count, memory_order_relaxed)); /* Signal everything to stop simultaneously so scoring and reader wind down in parallel rather than sequentially. */ @@ -732,6 +1048,9 @@ static void async_session_destroy(void *ptr) { pthread_mutex_lock(&s->score_req_mu); free(s->score_req_filter); s->score_req_filter = NULL; + shared_idx_release(s->score_req_refine_idx); + s->score_req_refine_idx = NULL; + s->score_req_has_refine = false; s->score_req_stop = true; pthread_cond_signal(&s->score_req_cond); pthread_mutex_unlock(&s->score_req_mu); @@ -747,9 +1066,14 @@ static void async_session_destroy(void *ptr) { pthread_join(s->reader, NULL); if (s->fp) { fclose(s->fp); s->fp = NULL; } if (s->pid > 0) { waitpid(s->pid, NULL, 0); s->pid = -1; } + + cache_destroy(&s->cache); + pthread_mutex_lock(&s->mu); arena_free(&s->arena); - free(s->cands); + for (size_t i = 0; i < CANDS_TOP_CAP; i++) { + if (s->cands_top[i]) { free(s->cands_top[i]); s->cands_top[i] = NULL; } + } pthread_mutex_unlock(&s->mu); pthread_mutex_destroy(&s->mu); free(s); @@ -822,16 +1146,17 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, s->pid = pid; s->fp = fdopen(pfd[0], "r"); - s->cap = ASYNC_INIT_CAP; - s->cands = malloc(s->cap * sizeof *s->cands); + /* cands_top[] was zeroed by calloc(); blocks are allocated lazily by + the reader. No upfront pointer-array allocation. */ pthread_mutex_init(&s->mu, NULL); pthread_mutex_init(&s->score_req_mu, NULL); pthread_cond_init(&s->score_req_cond, NULL); pthread_mutex_init(&s->score_res_mu, NULL); + cache_init(&s->cache); atomic_store(&s->gen, 0); atomic_store(&s->score_abort, false); - if (!s->fp || !s->cands || + if (!s->fp || pthread_create(&s->reader, NULL, async_reader, s) != 0 || pthread_create(&s->score_thread, NULL, scoring_thread_fn, s) != 0) { async_session_destroy(s); @@ -847,7 +1172,8 @@ fzf_native_async_stop(emacs_env *env, ptrdiff_t nargs, (void)nargs; AsyncSession *s = env->get_user_ptr(env, args[0]); if (s) { - fzf_log("async_stop: pid=%d total=%zu\n", (int)s->pid, s->count); + fzf_log("async_stop: pid=%d total=%zu\n", (int)s->pid, + (size_t)atomic_load_explicit(&s->count, memory_order_relaxed)); env->set_user_ptr(env, args[0], NULL); async_session_destroy(s); } @@ -895,44 +1221,98 @@ static void counting_sort_scored(ScoredStr *xs, size_t n) { free(count); } -struct AsyncScoringBatch { - unsigned len; - ScoredStr xs[BATCH_SIZE]; +/* Lightweight work unit: a contiguous range of virtual indices to score. + No string data is embedded — workers resolve candidates from cands_top. */ +struct AsyncScoringRange { + size_t from; + size_t to; }; +/* Shared read-only context for all scoring workers. */ struct AsyncScoringShared { - fzf_pattern_t *pattern; - struct AsyncScoringBatch *batches; + AsyncSession *s; + fzf_pattern_t *pattern; + _Atomic bool *stop; + + /* Refine context: scattered previously-matched entries (bounded by LIMIT). + NULL for full-pool runs. */ + char **refine_snap; + uint32_t *refine_idx_snap; + size_t refine_count; + size_t refine_delta_from; + bool has_refine; + + /* Work distribution */ + struct AsyncScoringRange *ranges; _Atomic ssize_t remaining; - _Atomic bool *stop; /* points to session's score_abort */ + + /* Per-worker result cap (0 = unlimited). Set to a multiple of + ceil(limit/num_workers) to prevent memory exhaustion on broad queries. */ + size_t per_worker_limit; +}; + +/* Per-worker state: each spawned thread gets its own copy. */ +struct AsyncWorkerArgs { + struct AsyncScoringShared *shared; + ScoredStr *results; + size_t result_count; + size_t result_cap; }; static void *async_scoring_worker(void *ptr) { - struct AsyncScoringShared *shared = ptr; - fzf_slab_t *slab = fzf_make_default_slab(); - fzf_pattern_t *pattern = shared->pattern; + struct AsyncWorkerArgs *args = ptr; + struct AsyncScoringShared *shared = args->shared; + AsyncSession *s = shared->s; + fzf_pattern_t *pattern = shared->pattern; + fzf_slab_t *slab = fzf_make_default_slab(); - ssize_t bi; - while ((bi = atomic_fetch_sub_explicit(&shared->remaining, 1, + ssize_t ri; + while ((ri = atomic_fetch_sub_explicit(&shared->remaining, 1, memory_order_seq_cst) - 1) >= 0) { if (shared->stop && atomic_load_explicit(shared->stop, memory_order_relaxed)) break; - struct AsyncScoringBatch *batch = shared->batches + bi; - unsigned n = 0; + if (shared->per_worker_limit && args->result_count >= shared->per_worker_limit) + break; + + struct AsyncScoringRange *range = &shared->ranges[ri]; bool aborted = false; - for (unsigned i = 0; i < batch->len; i++) { + for (size_t i = range->from; i < range->to; i++) { if ((i & 0xFF) == 0 && shared->stop && atomic_load_explicit(shared->stop, memory_order_relaxed)) { aborted = true; break; } - int sc = pattern ? fzf_get_score(batch->xs[i].str, pattern, slab) : 1; - if (!pattern || sc > 0) { - batch->xs[n] = batch->xs[i]; - batch->xs[n++].score = sc; + + /* Resolve candidate: scattered refine entries first, then cands_top. */ + const char *str; + uint32_t orig_idx; + if (shared->has_refine && i < shared->refine_count) { + str = shared->refine_snap[i]; + orig_idx = shared->refine_idx_snap[i]; + } else { + size_t gi = shared->has_refine + ? shared->refine_delta_from + (i - shared->refine_count) + : i; + str = s->cands_top[gi >> CANDS_BLOCK_SHIFT][gi & CANDS_BLOCK_MASK]; + orig_idx = (uint32_t)gi; } + + int sc = pattern ? fzf_get_score(str, pattern, slab) : 1; + if (pattern && sc <= 0) continue; + + if (args->result_count >= args->result_cap) { + size_t newcap = args->result_cap ? args->result_cap * 2 : 64; + ScoredStr *nb = realloc(args->results, newcap * sizeof *nb); + if (!nb) { aborted = true; break; } + args->results = nb; + args->result_cap = newcap; + } + args->results[args->result_count++] = + (ScoredStr){ .str = (char *)str, .score = sc, .idx = orig_idx }; + + if (shared->per_worker_limit && args->result_count >= shared->per_worker_limit) + break; } if (aborted) break; - batch->len = n; } fzf_free_slab(slab); @@ -954,6 +1334,13 @@ static void *scoring_thread_fn(void *arg) { char *filter = s->score_req_filter; /* steal ownership */ size_t limit = s->score_req_limit; s->score_req_filter = NULL; + /* Phase 2: also steal refine params (NULL if no refine). */ + bool has_refine = s->score_req_has_refine; + SharedIdx *refine_sidx = s->score_req_refine_idx; + size_t refine_delta_from = s->score_req_refine_delta_from; + s->score_req_refine_idx = NULL; + s->score_req_refine_delta_from = 0; + s->score_req_has_refine = false; /* Record what we're about to score so main thread can skip abort for same filter */ free(s->score_current_filter); s->score_current_filter = strdup(filter); @@ -964,102 +1351,155 @@ static void *scoring_thread_fn(void *arg) { next dispatch that may have already set it again. */ atomic_store_explicit(&s->score_abort, false, memory_order_seq_cst); - /* Snapshot candidate count first (brief lock), then malloc outside lock, - then memcpy under lock. Keeps s->mu held only for the fast memcpy, - not for the potentially-slow malloc with tens of millions of candidates. */ + /* Capture current pool count (brief lock). */ pthread_mutex_lock(&s->mu); - size_t count = s->count; + size_t pool_count = atomic_load_explicit(&s->count, memory_order_relaxed); pthread_mutex_unlock(&s->mu); - char **snap = count ? malloc(count * sizeof *snap) : NULL; - if (!snap && count) { + /* Cap refine inputs against the current pool (defensive). */ + if (has_refine && refine_delta_from > pool_count) + refine_delta_from = pool_count; + size_t refine_count = has_refine ? refine_sidx->count : 0; + size_t delta_count = has_refine ? (pool_count - refine_delta_from) : 0; + size_t scount = has_refine ? (refine_count + delta_count) : pool_count; + + /* Allocate only the small arrays for scattered refine indices (bounded by + LIMIT ≤ 10000). Workers read full-pool and delta candidates directly + from cands_top — no O(pool_count) snapshot or batch array is needed. */ + char **refine_snap = refine_count ? malloc(refine_count * sizeof *refine_snap) : NULL; + uint32_t *refine_idx_snap = refine_count ? malloc(refine_count * sizeof *refine_idx_snap) : NULL; + if (refine_count && (!refine_snap || !refine_idx_snap)) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(filter); continue; + free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); continue; } pthread_mutex_lock(&s->mu); - if (s->count < count) count = s->count; /* cap if reader shrank (shouldn't happen) */ - if (snap) memcpy(snap, s->cands, count * sizeof *snap); - pthread_mutex_unlock(&s->mu); - - /* Batch; check abort every 64 K items so a filter change is noticed quickly. */ - struct AsyncScoringBatch *batches = NULL; - size_t bi = 0, bcap = 0; - bool batch_ok = true; - for (size_t i = 0; i < count; i++) { - if ((i & 0xFFFF) == 0 && - atomic_load_explicit(&s->score_abort, memory_order_relaxed)) { - batch_ok = false; break; - } - if (!batches || (batches[bi].len >= BATCH_SIZE && ++bi >= bcap)) { - bcap = bcap ? bcap * 2 : 1; - struct AsyncScoringBatch *nb = realloc(batches, bcap * sizeof *nb); - if (!nb) { batch_ok = false; break; } - for (size_t k = bi; k < bcap; k++) nb[k].len = 0; - batches = nb; + if (has_refine) { + for (size_t k = 0; k < refine_count; k++) { + uint32_t i = refine_sidx->idx[k]; + refine_snap[k] = (i < s->count) + ? s->cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK] + : ""; + refine_idx_snap[k] = i; } - batches[bi].xs[batches[bi].len].str = snap[i]; - batches[bi].xs[batches[bi].len].score = 0; - batches[bi].len++; + if (s->count < pool_count) pool_count = s->count; + if (delta_count > pool_count - refine_delta_from) + delta_count = pool_count - refine_delta_from; + scount = refine_count + delta_count; + } else { + if (s->count < pool_count) pool_count = scount = s->count; } - if (!batch_ok) { + pthread_mutex_unlock(&s->mu); + + /* Build a range array — each entry is 16 bytes, no string pointers. + Workers resolve candidates from cands_top at score time. */ + size_t num_ranges = scount ? (scount + BATCH_SIZE - 1) / BATCH_SIZE : 0; + struct AsyncScoringRange *ranges = num_ranges ? malloc(num_ranges * sizeof *ranges) : NULL; + if (num_ranges && !ranges) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(snap); free(filter); free(batches); continue; + free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); continue; + } + for (size_t r = 0; r < num_ranges; r++) { + ranges[r].from = r * BATCH_SIZE; + ranges[r].to = (r + 1) * BATCH_SIZE <= scount ? (r + 1) * BATCH_SIZE : scount; } - unsigned num_batches = count ? (unsigned)(bi + 1) : 0; - unsigned max_workers = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); + /* Reserve one core for Emacs's event loop and idle timers. */ + unsigned ncpu = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); + unsigned max_workers = ncpu > 1 ? ncpu - 1 : 1; size_t flen = strlen(filter); fzf_pattern_t *pattern = flen ? fzf_parse_pattern(CaseIgnore, false, filter, true) : NULL; bool has_pattern = (pattern != NULL); - struct AsyncScoringShared shared = { - .pattern = pattern, - .batches = batches, - .remaining = num_batches, - .stop = &s->score_abort, + /* Cap per-worker output to prevent memory exhaustion on broad/empty queries. + 4× slack so results don't cluster in a subset of workers. */ + unsigned num_workers_to_spawn = (unsigned)MIN(max_workers, num_ranges); + size_t per_worker_limit = (limit && num_workers_to_spawn) + ? (limit / num_workers_to_spawn + 1) * 4 : 0; + + struct AsyncScoringShared ws = { + .s = s, + .pattern = pattern, + .stop = &s->score_abort, + .refine_snap = refine_snap, + .refine_idx_snap = refine_idx_snap, + .refine_count = refine_count, + .refine_delta_from = refine_delta_from, + .has_refine = has_refine, + .ranges = ranges, + .remaining = (ssize_t)num_ranges, + .per_worker_limit = per_worker_limit, }; - pthread_t *threads = malloc(max_workers * sizeof *threads); - unsigned num_workers = 0; - if (threads && num_batches) { - for (; num_workers < MIN(max_workers, num_batches); num_workers++) - pthread_create(threads + num_workers, NULL, async_scoring_worker, &shared); + struct AsyncWorkerArgs *worker_args = num_workers_to_spawn + ? malloc(num_workers_to_spawn * sizeof *worker_args) : NULL; + pthread_t *threads = num_workers_to_spawn + ? malloc(num_workers_to_spawn * sizeof *threads) : NULL; + unsigned num_workers = 0; + if (worker_args && threads && num_ranges) { + for (unsigned w = 0; w < num_workers_to_spawn; w++) + worker_args[w] = (struct AsyncWorkerArgs){ .shared = &ws }; + for (; num_workers < num_workers_to_spawn; num_workers++) + pthread_create(&threads[num_workers], NULL, async_scoring_worker, + &worker_args[num_workers]); } for (unsigned i = 0; i < num_workers; i++) pthread_join(threads[i], NULL); free(threads); if (pattern) fzf_free_pattern(pattern); - /* If a different filter arrived while we were scoring, discard partial results. */ + /* If a different filter arrived while we were scoring, discard results. */ if (atomic_load_explicit(&s->score_abort, memory_order_relaxed)) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(snap); free(batches); free(filter); + if (worker_args) + for (unsigned w = 0; w < num_workers; w++) free(worker_args[w].results); + free(worker_args); free(ranges); + free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); continue; } - /* Compact into flat array */ - size_t total = 0; - for (unsigned i = 0; i < num_batches; i++) total += batches[i].len; - - ScoredStr *flat = total ? malloc(total * sizeof *flat) : NULL; + /* Merge per-worker result buffers into a single flat array. */ size_t pos = 0; - if (flat) { - for (unsigned i = 0; i < num_batches; i++) { - struct AsyncScoringBatch *b = batches + i; - for (unsigned j = 0; j < b->len; j++) - flat[pos++] = b->xs[j]; + if (worker_args) + for (unsigned w = 0; w < num_workers; w++) pos += worker_args[w].result_count; + + ScoredStr *flat = pos ? malloc(pos * sizeof *flat) : NULL; + size_t flat_pos = 0; + if (flat && worker_args) { + for (unsigned w = 0; w < num_workers; w++) { + memcpy(flat + flat_pos, worker_args[w].results, + worker_args[w].result_count * sizeof *flat); + flat_pos += worker_args[w].result_count; } - if (has_pattern && pos > 1) - counting_sort_scored(flat, pos); } + if (worker_args) + for (unsigned w = 0; w < num_workers; w++) free(worker_args[w].results); + free(worker_args); + free(ranges); + + /* Capture the full matched-index set BEFORE counting_sort + reorders flat[]. The cache stores this for prefix-refinement + scoring of extending queries (Phase 2). */ + uint32_t *matched_idx = NULL; + size_t match_count = pos; + if (pos) { + matched_idx = malloc(pos * sizeof *matched_idx); + if (matched_idx) { + for (size_t i = 0; i < pos; i++) matched_idx[i] = flat[i].idx; + } else { + match_count = 0; /* alloc failure: cache entry will lack the set */ + } + } + + if (flat && has_pattern && pos > 1) + counting_sort_scored(flat, pos); size_t emit = (limit && limit < pos) ? limit : pos; @@ -1073,16 +1513,25 @@ static void *scoring_thread_fn(void *arg) { s->score_results = flat; s->score_count = emit; s->last_filtered = pos; - s->last_total = count; + s->last_total = pool_count; pthread_mutex_unlock(&s->score_res_mu); + /* Cache what we just published, keyed by filter, tagged with the + candidate-pool size at score time. Subsequent dispatches with + the same filter at the same pool size skip scoring entirely. + matched_idx carries the full match set for prefix-refinement + scoring of extending queries. */ + cache_insert(&s->cache, filter, pool_count, flat, emit, + matched_idx, match_count); + free(matched_idx); + /* Increment gen so Elisp knows new results are available */ atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); - fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu\n", - filter, pos, count, emit); + fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu refine=%d\n", + filter, pos, pool_count, emit, has_refine ? 1 : 0); - free(snap); free(batches); free(filter); + free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); } fzf_log("scoring_thread EXIT\n"); @@ -1111,31 +1560,79 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, fzf_log("async_candidates: filter='%s' limit=%zu — dispatching to bg thread\n", filter, limit); - /* Enqueue the new request. Only abort in-flight scoring if the filter - actually changed — same-filter timer re-triggers must not interrupt a - scoring run that is still working on the same query, which would cause - a livelock where scoring never completes on large candidate sets. */ - pthread_mutex_lock(&s->score_req_mu); - bool filter_changed = !(s->score_current_filter && - strcmp(s->score_current_filter, filter) == 0 && - s->score_current_limit == limit); - if (filter_changed) - atomic_store_explicit(&s->score_abort, true, memory_order_seq_cst); - free(s->score_req_filter); - s->score_req_filter = filter; /* scoring thread owns this now */ - s->score_req_limit = limit; - pthread_cond_signal(&s->score_req_cond); - pthread_mutex_unlock(&s->score_req_mu); + /* Read the current candidate-pool size so we can decide whether a + cache hit is fresh (same pool size) or stale (pool grew since). + count is _Atomic: no lock needed, so this call never blocks on + s->mu even when the reader is in the middle of a large realloc. */ + size_t current_count = atomic_load_explicit(&s->count, memory_order_acquire); + + /* Cache lookup. Three outcomes: + - exact fresh: return cached top, no scoring scheduled. + - exact stale: return cached top, schedule refine using this + entry's matched_idx + delta to current count. + - prefix match: return prefix entry's top (a superset of Q's + results), schedule refine using prefix entry's + matched_idx + delta. + - miss: return current score_results (today's behavior), + schedule full-pool scoring. */ + ScoredStr *snap = NULL; + size_t rcount = 0; + size_t entry_pool_gen = 0; + SharedIdx *entry_sidx = NULL; + + bool exact = cache_lookup_exact(&s->cache, filter, + &snap, &rcount, + &entry_sidx, + &entry_pool_gen); + bool prefix = false; + if (!exact) { + prefix = cache_lookup_prefix(&s->cache, filter, + &snap, &rcount, + &entry_sidx, + &entry_pool_gen); + } + bool exact_fresh = exact && entry_pool_gen == current_count; + bool can_refine = (exact || prefix) && entry_sidx != NULL; - /* Copy latest scored results under lock so we can release quickly */ - pthread_mutex_lock(&s->score_res_mu); - size_t rcount = s->score_count; - ScoredStr *snap = rcount ? malloc(rcount * sizeof *snap) : NULL; - if (snap && s->score_results) - memcpy(snap, s->score_results, rcount * sizeof *snap); - else - rcount = 0; - pthread_mutex_unlock(&s->score_res_mu); + if (exact_fresh) { + /* Cache fully satisfied: nothing to score. */ + free(filter); + shared_idx_release(entry_sidx); + } else { + /* Schedule scoring. If we have a prefix or stale-exact hit, pass + the entry's matched_idx + pool_gen as refinement params so the + scoring thread restricts its work to {entry's matches ∪ delta + since entry was scored}. */ + pthread_mutex_lock(&s->score_req_mu); + bool filter_changed = !(s->score_current_filter && + strcmp(s->score_current_filter, filter) == 0 && + s->score_current_limit == limit); + if (filter_changed) + atomic_store_explicit(&s->score_abort, true, memory_order_seq_cst); + free(s->score_req_filter); + s->score_req_filter = filter; + s->score_req_limit = limit; + shared_idx_release(s->score_req_refine_idx); + s->score_req_refine_idx = can_refine ? entry_sidx : NULL; + s->score_req_refine_delta_from = can_refine ? entry_pool_gen : 0; + s->score_req_has_refine = can_refine; + pthread_cond_signal(&s->score_req_cond); + pthread_mutex_unlock(&s->score_req_mu); + /* If we didn't pass entry_sidx into the refine slot, release it. */ + if (!can_refine) shared_idx_release(entry_sidx); + } + + /* Cache miss (no exact, no prefix): fall back to score_results. */ + if (!exact && !prefix) { + pthread_mutex_lock(&s->score_res_mu); + rcount = s->score_count; + snap = rcount ? malloc(rcount * sizeof *snap) : NULL; + if (snap && s->score_results) + memcpy(snap, s->score_results, rcount * sizeof *snap); + else + rcount = 0; + pthread_mutex_unlock(&s->score_res_mu); + } /* Build Emacs list from stale results — strings are stable until session destroy */ emacs_value result = Qnil; From ff17a444d4d5b36d9aeb1a1caf2cc52da34af9e1 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:39 -0400 Subject: [PATCH 02/47] Implement match-position highlighting in the C module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Background: fzf-async previously delegated candidate highlighting to Elisp via completion-pcm--hilit-commonality. For a 32-character query, the Elisp helper fzf-async--make-fzf-highlight-pattern built a 63-element PCM pattern of interleaved `any' and character tokens. Emacs compiled this to a regex with 32 nested .* groups, causing exponential backtracking on non-matching candidates — the common case for most results. With 1038 candidates and a 32-char query this produced an 18-second hang on the Emacs main thread, confirmed in the C-side debug log (18-second gap at the fzf_native_async_candidates call site, all other threads idle). Fix: highlight from C after building the result list, using the already-compiled fzf_pattern_t and fzf_get_positions. Implementation in fzf_native_async_candidates: Two defcustoms are read via symbol-value at the top of each call: fzf-async-highlight — nil disables; non-nil enables fzf-async-highlight-max-candidates — cap on candidates highlighted (later merged into fzf-async-highlight as the tri-value nil/t/N form) A fresh fzf_pattern_t is parsed from filter_for_hilit (a strdup of the filter string taken before filter ownership transfers to the scoring thread), and one fzf_slab_t is allocated for the highlight pass. For each of the top min(rcount, highlight_cap) candidates: - fzf_get_positions(str, pattern, slab) returns pos->data[] in descending byte-offset order (highest position first). - The loop iterates ascending (index from size-1 to 0), merging adjacent offsets into contiguous runs. - One put-text-property call per run applies face=completions-common-part to the Emacs string object before it is consed into the result list. Merging runs minimises the number of text-property operations, which matters most for dense matches (e.g. exact-substring queries). Two global emacs_value handles — Qface and Qcompletions_common_part — are interned once at module init and reused on every call. Pattern and slab are freed before returning. filter_for_hilit is always freed regardless of whether highlighting is enabled. Complexity: O(query_len × highlight_cap) vs O(2^query_len × N) for the PCM regex path. For 200 candidates and a 32-char query: ~6400 fzf position lookups vs. the exponential blowup that caused the 18-second hang. Note: fzf positions are byte offsets; put-text-property uses character positions. For ASCII candidates (the overwhelming majority of find/rg/git output) these are identical. Multi-byte UTF-8 candidates may have slightly misaligned face ranges — the same limitation shared by fussy's fzf-based highlighting. --- fzf-native-module.c | 76 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index e2d4189..00f3b4d 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -92,6 +92,7 @@ emacs_value Fhashtablep, Fmessage, Fvectorp, Fconsp, Fcdr, Fcar, Fvconcat; emacs_value Ffunctionp, Fsymbolp, Fsymbolname, Flength, Fnth, Fprinc, Freverse; emacs_value Qcompletion_score, Fput_text_property, Qzero, Qone; emacs_value Fencode_coding_string, Qutf_8; +emacs_value Qface, Qcompletions_common_part; /** An Emacs string made accessible by copying. */ @@ -1552,6 +1553,8 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, char *filter = malloc((size_t)flen); if (!filter) return Qnil; env->copy_string_contents(env, args[1], filter, &flen); + /* Keep a copy for C-side highlighting; filter ownership may transfer below. */ + char *filter_for_hilit = (flen > 1) ? strdup(filter) : NULL; size_t limit = 0; if (nargs > 2 && !env->eq(env, args[2], Qnil)) @@ -1634,7 +1637,40 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, pthread_mutex_unlock(&s->score_res_mu); } - /* Build Emacs list from stale results — strings are stable until session destroy */ + /* Resolve C-side highlight cap from defcustoms fzf-async-highlight and + fzf-async-highlight-max-candidates. Both are read via symbol-value so + the user can change them without reloading the module. */ + size_t hl_cap = 0; + fzf_pattern_t *hl_pattern = NULL; + fzf_slab_t *hl_slab = NULL; + + if (filter_for_hilit) { + emacs_value sym_hi = env->intern(env, "fzf-async-highlight"); + emacs_value hi = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym_hi); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (env->eq(env, hi, Qt)) + hl_cap = rcount; /* t → highlight everything */ + else if (!env->eq(env, hi, Qnil)) { /* integer → highlight top N */ + intmax_t n = env->extract_integer(env, hi); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (n > 0) + hl_cap = (size_t)n; + } + /* nil or negative integer → hl_cap stays 0, no highlighting */ + } + if (hl_cap > 0) { + hl_pattern = fzf_parse_pattern(CaseIgnore, false, filter_for_hilit, true); + hl_slab = fzf_make_default_slab(); + } + + /* Build Emacs list from stale results — strings are stable until session destroy. + snap[0] is the highest-scoring candidate (prepend loop puts it at list head). + Apply fzf_get_positions highlighting for snap[0..hl_cap-1] (i < hl_cap). + NOTE: fzf positions are byte offsets; put-text-property uses character + positions. For ASCII candidates these are identical. Multi-byte UTF-8 + candidates may have slightly misaligned highlights — acceptable for now. */ emacs_value result = Qnil; for (size_t i = rcount; i-- > 0;) { emacs_value str = env->make_string(env, snap[i].str, @@ -1646,11 +1682,47 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, } else if (status != emacs_funcall_exit_return) { break; } + + if (hl_pattern && i < hl_cap) { + fzf_position_t *pos = fzf_get_positions(snap[i].str, hl_pattern, hl_slab); + if (pos && pos->size > 0) { + /* pos->data[] is in descending order: pos->data[0] = highest position. + Iterate ascending (j from size-1 to 0) to find contiguous runs. */ + size_t plen = pos->size; + size_t run_start = pos->data[plen - 1]; + size_t run_end = run_start; + for (ptrdiff_t j = (ptrdiff_t)plen - 2; j >= 0; j--) { + size_t p = pos->data[j]; + if (p == run_end + 1) { + run_end = p; + } else { + emacs_value a[5] = { + env->make_integer(env, (intmax_t)run_start), + env->make_integer(env, (intmax_t)(run_end + 1)), + Qface, Qcompletions_common_part, str }; + env->funcall(env, Fput_text_property, 5, a); + env->non_local_exit_clear(env); + run_start = run_end = p; + } + } + emacs_value a[5] = { + env->make_integer(env, (intmax_t)run_start), + env->make_integer(env, (intmax_t)(run_end + 1)), + Qface, Qcompletions_common_part, str }; + env->funcall(env, Fput_text_property, 5, a); + env->non_local_exit_clear(env); + } + fzf_free_positions(pos); + } + result = env->funcall(env, Fcons, 2, (emacs_value[]){ str, result }); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) break; } + if (hl_pattern) fzf_free_pattern(hl_pattern); + if (hl_slab) fzf_free_slab(hl_slab); + free(filter_for_hilit); free(snap); return result; } @@ -1811,6 +1883,8 @@ int emacs_module_init(struct emacs_runtime *rt) { Qcompletion_score = env->make_global_ref(env, env->intern(env, "completion-score")); Fput_text_property = env->make_global_ref(env, env->intern(env, "put-text-property")); Fencode_coding_string = env->make_global_ref(env, env->intern(env, "encode-coding-string")); + Qface = env->make_global_ref(env, env->intern(env, "face")); + Qcompletions_common_part = env->make_global_ref(env, env->intern(env, "completions-common-part")); Qutf_8 = env->make_global_ref(env, env->intern(env, "utf-8")); Qlistofzero = env->make_global_ref( env, env->funcall(env, Fcons, 2, From 8d08ec50e8b7589508d9d8e76ff554e3da819ffa Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:43 -0400 Subject: [PATCH 03/47] Implement support for fzf-async-max-line-length defcustom in async reader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Long lines — minified JavaScript, base64 payloads, binary-adjacent grep output — slow fzf_get_score (more characters to align), inflate the arena, and produce unreadable candidates in the completion UI. This commit adds a configurable line-length gate applied in the reader thread, before any candidate enters the pool. The gate is controlled by fzf-async-max-line-length, a defcustom read by fzf_native_async_start on the main thread: nil no limit (default, preserves current behavior) t built-in default of 512 characters +N exclude lines longer than N characters -N include but truncate to N characters The value is converted to a signed ptrdiff_t stored as AsyncSession.max_line_length (0 = off, >0 = exclude, <0 = truncate). The write completes before pthread_create, so the reader thread sees the value with no additional synchronization. In async_reader, after ANSI stripping: ptrdiff_t mll = s->max_line_length; if (mll != 0) { ptrdiff_t cap = mll > 0 ? mll : -mll; if ((ptrdiff_t)len > cap) { if (mll > 0) continue; // exclude len = (size_t)cap; // truncate line[len] = '\0'; } } Truncation writes into the on-stack line[] buffer before arena_strdup, so the arena never stores more than cap characters per candidate. Exclusion skips the arena_strdup and cands_top append entirely. Character vs. byte: strlen() is used for the length check. For the common case (ASCII output from find, rg, git ls-files) bytes equal characters. For mixed-script content the check is conservative — it may admit lines slightly longer in character count than the configured cap. This matches user intuition for the primary use case. --- fzf-native-module.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/fzf-native-module.c b/fzf-native-module.c index 00f3b4d..11d6318 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -984,6 +984,10 @@ typedef struct { size_t score_count; /* number of entries in score_results */ Cache cache; /* per-session result cache */ + + /* Read-only after session start; set from fzf-async-max-line-length defcustom. + 0 = no limit. >0 = exclude lines longer than N chars. <0 = truncate to |N|. */ + ptrdiff_t max_line_length; } AsyncSession; static void *async_reader(void *arg) { @@ -997,6 +1001,16 @@ static void *async_reader(void *arg) { len = async_strip_ansi(line, len); if (!len) continue; + ptrdiff_t mll = s->max_line_length; + if (mll != 0) { + ptrdiff_t cap = mll > 0 ? mll : -mll; + if ((ptrdiff_t)len > cap) { + if (mll > 0) continue; /* exclude */ + len = (size_t)cap; /* truncate */ + line[len] = '\0'; + } + } + char *dup = arena_strdup(&s->arena, line, len); if (!dup) continue; @@ -1157,6 +1171,22 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, atomic_store(&s->gen, 0); atomic_store(&s->score_abort, false); + { + emacs_value sym = env->intern(env, "fzf-async-max-line-length"); + emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (env->eq(env, val, Qt)) + s->max_line_length = 512; + else if (!env->eq(env, val, Qnil)) { + s->max_line_length = (ptrdiff_t)env->extract_integer(env, val); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + s->max_line_length = 0; + } + } + } + if (!s->fp || pthread_create(&s->reader, NULL, async_reader, s) != 0 || pthread_create(&s->score_thread, NULL, scoring_thread_fn, s) != 0) { From f9a255d1450f9fd6690630fca8452be2161f9d6c Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:45 -0400 Subject: [PATCH 04/47] Implement highlighting for 1-candidate and batch case --- fzf-native-module.c | 119 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 99 insertions(+), 20 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 11d6318..251779f 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -297,6 +297,72 @@ static void *worker_routine(void *ptr) { return NULL; } +/* Apply `completions-common-part' face to STR_VAL on positions matched by + PATTERN against CSTR. Computes positions via fzf_get_positions and groups + them into contiguous runs to minimize put-text-property calls. + + Byte offsets from fzf are used directly as character positions: accurate + for ASCII, may be slightly misaligned for multi-byte UTF-8 (same caveat + as the async highlight path). */ +static void apply_highlight_positions(emacs_env *env, + const char *cstr, + fzf_pattern_t *pattern, + fzf_slab_t *slab, + emacs_value str_val) { + fzf_position_t *pos = fzf_get_positions(cstr, pattern, slab); + if (pos && pos->size > 0) { + /* pos->data[] is in descending order: pos->data[0] = highest position. + Iterate ascending (j from size-1 to 0) to find contiguous runs. */ + size_t plen = pos->size; + size_t run_start = pos->data[plen - 1]; + size_t run_end = run_start; + for (ptrdiff_t j = (ptrdiff_t)plen - 2; j >= 0; j--) { + size_t p = pos->data[j]; + if (p == run_end + 1) { + run_end = p; + } else { + emacs_value a[5] = { + env->make_integer(env, (intmax_t)run_start), + env->make_integer(env, (intmax_t)(run_end + 1)), + Qface, Qcompletions_common_part, str_val }; + env->funcall(env, Fput_text_property, 5, a); + env->non_local_exit_clear(env); + run_start = run_end = p; + } + } + emacs_value a[5] = { + env->make_integer(env, (intmax_t)run_start), + env->make_integer(env, (intmax_t)(run_end + 1)), + Qface, Qcompletions_common_part, str_val }; + env->funcall(env, Fput_text_property, 5, a); + env->non_local_exit_clear(env); + } + fzf_free_positions(pos); +} + +/* Read fussy-fzf-native-highlight via symbol-value and resolve to a cap. + Returns: + 0 — no highlighting (nil, negative, unreadable, or zero). + LEN — highlight all (t). + N — highlight top N (clamped to LEN). */ +static size_t resolve_fussy_highlight_cap(emacs_env *env, size_t len) { + emacs_value sym = env->intern(env, "fussy-fzf-native-highlight"); + emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + return 0; + } + if (env->eq(env, v, Qnil)) return 0; + if (env->eq(env, v, Qt)) return len; + intmax_t n = env->extract_integer(env, v); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + return 0; + } + if (n <= 0) return 0; + return (size_t)n > len ? len : (size_t)n; +} + // fzf-native-score-all COLLECTION QUERY &optional SLAB emacs_value fzf_native_score_all(emacs_env *env, ptrdiff_t nargs, @@ -423,11 +489,23 @@ emacs_value fzf_native_score_all(emacs_env *env, counting_sort_candidates(xs, len); + /* Resolve C-side highlight cap from fussy-fzf-native-highlight. After the + sort, xs[0] is the highest-scoring candidate, so the top-N candidates + are xs[0..hl_cap-1]. The original parsing pattern was already freed; + re-parse for highlighting (case-insensitive matches the async path). */ + size_t hl_cap = resolve_fussy_highlight_cap(env, len); + fzf_pattern_t *hl_pattern = NULL; + fzf_slab_t *hl_slab = NULL; + if (hl_cap > 0) { + hl_pattern = fzf_parse_pattern(CaseIgnore, false, query.b, true); + if (hl_pattern) hl_slab = fzf_make_default_slab(); + if (!hl_slab) { + if (hl_pattern) { fzf_free_pattern(hl_pattern); hl_pattern = NULL; } + hl_cap = 0; + } + } + for (size_t i = len; i-- > 0;) { - /* printf("zero: %jd one: %jd score: %d", */ - /* env->extract_integer(env, Qzero), */ - /* env->extract_integer(env, Qone), */ - /* xs[i].score); */ /* e.g. (put-text-property 0 1 'completion-score score x) */ if (xs[i].s.len > 0) { env->funcall(env, Fput_text_property, 5, @@ -438,9 +516,17 @@ emacs_value fzf_native_score_all(emacs_env *env, }); } + if (hl_pattern && i < hl_cap) { + apply_highlight_positions(env, xs[i].s.b, hl_pattern, hl_slab, + xs[i].value); + } + result = env->funcall(env, Fcons, 2, (emacs_value[]) { xs[i].value, result }); } + if (hl_pattern) fzf_free_pattern(hl_pattern); + if (hl_slab) fzf_free_slab(hl_slab); + fzf_log("fzf_native_score_all DONE: query='%.*s' count=%zu\n", (int)query.len, query.b, n); free(xs); @@ -535,26 +621,19 @@ emacs_value fzf_native_score(emacs_env *env, ptrdiff_t nargs, emacs_value args[] slab = fzf_make_default_slab(); } - /* You can get the score/position for as many items as you want */ int score = fzf_get_score(str.b, pattern, slab); - fzf_position_t *pos = fzf_get_positions(str.b, pattern, slab); - size_t offset = 1; - size_t len = 0; - if (pos) { - len = pos->size; + /* Apply C-layer highlighting when fussy-fzf-native-highlight is non-nil + and the candidate matched. The cap concept does not apply to a single + candidate — any non-nil value enables highlighting for this call. */ + if (score > 0 && resolve_fussy_highlight_cap(env, 1) > 0) { + apply_highlight_positions(env, str.b, pattern, slab, args[0]); } - emacs_value *result_array = malloc(sizeof(emacs_value) * (offset + len)); - - result_array[0] = env->make_integer(env, score); - - for (size_t i = 0; i < len; i++) { - result_array[offset + i] = env->make_integer(env, pos->data[len - (i + 1)]); - } - - result = env->funcall(env, Flist, offset + len, result_array); - fzf_free_positions(pos); + /* Return (SCORE) — a single-element list. Match indices are no longer + surfaced to Elisp; highlighting is handled in C. */ + emacs_value score_val = env->make_integer(env, score); + result = env->funcall(env, Flist, 1, &score_val); fzf_free_pattern(pattern); if (nargs > 2) { From adb91f400b4e7563d330bcc89b2c2c1c9e9b351c Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:48 -0400 Subject: [PATCH 05/47] Expose cache variable --- fzf-native-module.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 251779f..ef6fc0f 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -821,12 +821,14 @@ typedef struct { CacheEntry *head; /* most recently used */ CacheEntry *tail; /* least recently used */ size_t count; + size_t max_entries; /* set from fzf-async-cache-size at session start */ } Cache; -static void cache_init(Cache *c) { +static void cache_init(Cache *c, size_t max_entries) { pthread_mutex_init(&c->mu, NULL); c->head = c->tail = NULL; c->count = 0; + c->max_entries = max_entries ? max_entries : ASYNC_CACHE_MAX_ENTRIES; } static void cache_free_entry(CacheEntry *e) { @@ -1012,7 +1014,7 @@ static void cache_insert(Cache *c, const char *query, size_t pool_gen, fresh->pool_gen = pool_gen; CacheEntry *evicted = NULL; - if (c->count >= ASYNC_CACHE_MAX_ENTRIES) { + if (c->count >= c->max_entries) { evicted = c->tail; if (evicted) { cache_unlink(c, evicted); c->count--; } } @@ -1246,7 +1248,21 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, pthread_mutex_init(&s->score_req_mu, NULL); pthread_cond_init(&s->score_req_cond, NULL); pthread_mutex_init(&s->score_res_mu, NULL); - cache_init(&s->cache); + { + size_t cache_size = ASYNC_CACHE_MAX_ENTRIES; + emacs_value sym = env->intern(env, "fzf-async-cache-size"); + emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (!env->eq(env, val, Qnil)) { + intmax_t n = env->extract_integer(env, val); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (n > 0) + cache_size = (size_t)n; + } + cache_init(&s->cache, cache_size); + } atomic_store(&s->gen, 0); atomic_store(&s->score_abort, false); From e3cadaf6f0b0ab23cdff3596b99c15d5b6e4f6c7 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:02:52 -0400 Subject: [PATCH 06/47] Update architecture --- architecture.org | 189 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 139 insertions(+), 50 deletions(-) diff --git a/architecture.org b/architecture.org index c6958c0..f1eff72 100644 --- a/architecture.org +++ b/architecture.org @@ -269,25 +269,29 @@ ready." ** AsyncSession layout #+begin_src - ┌─────────────────┐ - │ AsyncSession │ - ├─────────────────┤ - │ s->cands[] │ ← protected by s->mu - │ s->count │ ← protected by s->mu - │ s->arena │ ← protected by s->mu (string storage) - │ s->gen (atomic) │ ← published on score completion - │ s->stop (atomic)│ ← shutdown signal - ├─────────────────┤ - │ score_req_* │ ← protected by score_req_mu - │ filter, limit │ - │ cond, abort │ - │ current_* │ - ├─────────────────┤ - │ score_results[] │ ← protected by score_res_mu - │ score_count │ - │ last_filtered │ - │ last_total │ - └─────────────────┘ + ┌──────────────────────────┐ + │ AsyncSession │ + ├──────────────────────────┤ + │ s->cands_top[][] │ ← two-level pointer table, under s->mu + │ s->count (atomic) │ ← candidate count; reader stores-release + │ s->arena │ ← bump allocator for candidate strings + │ s->gen (atomic) │ ← bumped on each score publish + │ s->stop (atomic) │ ← shutdown signal + │ s->max_line_length │ ← read-only after start; set from defcustom + ├──────────────────────────┤ + │ score_req_* │ ← protected by score_req_mu + │ filter, limit │ + │ cond, abort │ + │ current_filter/limit │ + │ refine_idx, delta_from │ ← LRU cache refinement params + ├──────────────────────────┤ + │ score_results[] │ ← protected by score_res_mu + │ score_count │ + │ last_filtered │ + │ last_total │ + ├──────────────────────────┤ + │ cache (LRU, 20 entries) │ ← per-session result cache + └──────────────────────────┘ #+end_src ** Threads, locks, ownership @@ -295,9 +299,9 @@ ready." | Thread | Reads | Writes | |---------------------+--------------------------------+--------------------------------------------| | Main (Elisp) | =score_results= (under res mu) | =score_req_*= (under req mu); signals cond | -| Reader | child fp | =s->cands=, =s->count=, =s->arena= (under s->mu) | -| Scorer | =s->cands= snapshot (under s->mu briefly), =score_req_*= | =score_results= (under res mu); =s->gen= | -| Scorer's sub-workers (transient) | their batch slice, =shared->pattern= (read-only) | their batch slice (compaction in-place) | +| Reader | child fp | =cands_top=, =s->count=, =s->arena= (under s->mu) | +| Scorer | =cands_top= directly (no lock; see safety note below), =score_req_*= | =score_results= (under res mu); =s->gen= | +| Scorer's sub-workers (transient) | their range slice of =cands_top= (read-only), =shared->pattern= (read-only) | per-worker result buffer | Three locks, distinct purposes: @@ -316,19 +320,36 @@ Locks are never nested. The scoring thread takes them sequentially: #+begin_src while (read line from child fp) { strip ANSI escapes (in place) - arena_strdup(line) ← arena owns the bytes - grow s->cands[] if at cap ← realloc, doubling - s->cands[s->count++] = ptr ← under s->mu + apply max_line_length gate (exclude or truncate) + arena_strdup(line) ← arena owns the bytes + resolve block: hi = count >> SHIFT ← two-level pointer table + lo = count & MASK + alloc block on first use (2 MB, lazy) ← under s->mu + s->cands_top[hi][lo] = ptr ← under s->mu + atomic_store(count, count+1) } on EOF: just return; thread exits naturally. #+end_src -Why an arena: candidates are written once and freed all at once at -session end. =arena_strdup= bumps a pointer into a 4 MB chunk; -=arena_free= walks chunks and frees them — O(chunks), not -O(candidates). For 60 M candidates this is the difference between -"instant teardown" and "seconds of individual =free()= calls when ESC -is pressed." +*Chunked candidate table:* rather than a single flat =char **= that must +be =realloc='d as it grows (causing VM-pressure stalls at tens of millions +of candidates), =cands_top= is a two-level pointer table. +=cands_top[CANDS_TOP_CAP]= holds up to 4096 block pointers; each block +holds =CANDS_BLOCK_SIZE= (256 K) candidate pointers (2 MB per block, allocated +lazily). The largest single allocation the reader ever makes is one 2 MB block, +regardless of pool size. + +*Max-line-length gate:* if =AsyncSession.max_line_length != 0=, lines +exceeding the cap are dropped (positive value) or truncated (negative value) +before =arena_strdup=. The value is read from the =fzf-async-max-line-length= +defcustom in =fzf_native_async_start= on the main thread before +=pthread_create=, so no locking is needed in the reader. + +*Arena:* candidates are written once and freed all at once at session end. +=arena_strdup= bumps a pointer into a 4 MB chunk; =arena_free= walks chunks +and frees them — O(chunks), not O(candidates). For 60 M candidates this is +the difference between instant teardown and seconds of individual =free()= +calls when ESC is pressed. ** Scoring thread — =scoring_thread_fn= @@ -341,24 +362,42 @@ a filter or sets =score_req_stop=. if score_req_stop: exit thread steal score_req_filter (set score_current_filter under req mu) atomic_store(score_abort, false) - parse pattern (once, on this thread) - snapshot s->cands pointers (brief s->mu, malloc outside lock) - spawn N worker pthreads, each scores its batch + parse pattern once (on this thread — strtok is not thread-safe) + read pool_count under s->mu + build range array: N ranges of BATCH_SIZE candidates each (16 B/range) + spawn M worker pthreads, each claims ranges atomically pthread_join all workers ← runs HERE, never on main thread if score_abort: ← filter changed mid-run free, clear current_filter, loop - compact survivors, counting_sort_scored, apply limit + merge per-worker result buffers, counting_sort_scored, apply limit publish to score_results (under res mu), update stats atomic_fetch_add(s->gen, 1) #+end_src -Workers check =score_abort= every 256 items inside the batch loop, not -just at batch boundaries. This bounds ESC latency at very large -candidate counts. - -The batch loop also checks abort every 64 K items during the -*snapshot construction* before workers spawn, so a filter change -during a 25 M-item memcpy doesn't force a full pre-work pass. +*Range-based workers:* each worker receives an array of =AsyncScoringRange +{from, to}= structs (16 B each) and claims ranges atomically via +=atomic_fetch_sub= on a shared =remaining= counter. Workers resolve +candidate pointers directly from =cands_top= at score time — no pre-copied +snapshot array needed. This eliminates the O(N) snapshot malloc (447 MB for +55 M candidates) and replaces the old O(N) batch array (~1 GB) with an +O(N/BATCH_SIZE) range array (~430 KB). + +*Worker result buffers:* each worker grows its own =ScoredStr *results= +buffer only as matches are found, bounded by +=ceil(limit / num_workers + 1) × 4= entries. For limit=10000 and 9 workers +this is ≤ 71 KB per worker, capping total scoring memory at ≤ 640 KB +regardless of pool size. + +*cands_top read safety:* workers read entries at indices =i < pool_count= +without holding =s->mu=. This is safe because: (a) entries below pool_count +are written by the reader exactly once and never modified thereafter; +(b) the scoring thread captures =pool_count= under =s->mu= before spawning +workers, establishing a happens-before edge; (c) the store-release on +=s->count= by the reader and the load-acquire by the scoring thread provide +the necessary memory ordering. + +Workers check =score_abort= every 256 items inside their scoring loop. This +bounds ESC latency even at very large candidate counts. ** The "same-filter, don't abort" rule @@ -393,19 +432,69 @@ cross-thread publish signal.) ** Memory and string ownership -| Object | Owner | Lifetime | -|-------------------------+----------------------------------+------------------------------------------------| -| Candidate strings | =s->arena= | Whole session — freed once at stop | -| =s->cands[]= (pointers) | =s->mu=-protected | Reallocated as it grows; freed at stop | -| =score_results[]= | =score_res_mu= | Replaced on each publish; final free at stop | -| =score_req_filter= | =score_req_mu= owns the slot | =strdup='d when stolen by scorer; freed before next steal | -| =score_current_filter= | Scoring thread owns; main thread reads under req mu | Cleared on every scoring exit path | -| =fzf_pattern_t= | Scoring thread | Parsed once per request, freed after workers join | +| Object | Owner | Lifetime | +|--------------------------------+----------------------------------+------------------------------------------------| +| Candidate strings | =s->arena= | Whole session — freed once at stop | +| =cands_top[i][j]= (pointers) | =s->mu=-protected writes; lock-free reads by workers | Blocks allocated on demand (2 MB each); freed at stop | +| =score_results[]= | =score_res_mu= | Replaced on each publish; final free at stop | +| =score_req_filter= | =score_req_mu= owns the slot | =strdup='d when stolen by scorer; freed before next steal | +| =score_current_filter= | Scoring thread owns; main thread reads under req mu | Cleared on every scoring exit path | +| =fzf_pattern_t= (scoring) | Scoring thread | Parsed once per request, freed after workers join | +| =fzf_pattern_t= (highlighting) | Main thread (in =fzf_native_async_candidates=) | Parsed per call for top-N highlight pass; freed before return | +| Per-worker =ScoredStr *= buffers | Each worker thread | Allocated when first match found; freed after merge | +| Cache =SharedIdx= (matched indices) | Reference-counted; shared between cache and score_req | Released when evicted or when refine slot is consumed | The arena is the key invariant: every char* exposed in =s->cands= or =score_results= points into arena memory and is valid until the =AsyncSession= is destroyed. +** Result cache (=fzf_native_async_candidates=) + +Each session maintains a 20-entry LRU cache mapping =(filter, pool_gen)= +to the scored snapshot from that run, retaining the matched candidate indices +as a =SharedIdx= (ref-counted shared array). + +On each =fzf_native_async_candidates= call, three outcomes are possible: + +| Outcome | Condition | Action | +|---------------+---------------------------------------------------+-----------------------------------------| +| Exact fresh | Filter matches and pool hasn't grown | Return cached snapshot; no scoring queued | +| Exact stale or prefix hit | Filter matches (or is an extension of a cached filter) but pool grew | Return cached snapshot; queue scoring restricted to =matched_idx ∪ [pool_gen..now)= | +| Miss | No matching cache entry | Return current =score_results=; queue full scoring | + +The refinement path ("exact stale" and "prefix") avoids re-scoring the +entire pool on each keystroke during incremental narrowing. When typing +"foo" after "fo", the cache hit for "fo" constrains the next scoring run to +the ~fo~ matches plus any candidates that arrived after that run completed. + +** C-side match highlighting (=fzf_native_async_candidates=) + +After building the result list from the scored snapshot, =fzf_native_async_candidates= +applies =completions-common-part= face to matched character runs directly on +the Emacs string objects, before they are consed into the return value. + +Flow: + +1. Read =fzf-async-highlight= via =symbol-value=: + - =nil= → skip; no pattern parsed. + - =t= → =hl_cap = rcount= (highlight everything). + - Integer N → =hl_cap = N= (highlight top N only). +2. If =hl_cap > 0=: parse =filter_for_hilit= (a =strdup= of the filter taken + before its ownership transfers to the scoring thread) into an + =fzf_pattern_t=; allocate one shared =fzf_slab_t=. +3. For =snap[0..hl_cap-1]= (the highest-scoring candidates, which end up at + the front of the Emacs list): + - =fzf_get_positions(str, pattern, slab)= → =pos->data[]= descending. + - Iterate ascending; merge adjacent byte offsets into contiguous runs. + - =put-text-property(run_start, run_end+1, face, completions-common-part, str)= + once per run. =Qface= and =Qcompletions_common_part= are global refs + interned once at module init. +4. Free pattern, slab, and =filter_for_hilit=. + +Candidates for index > =hl_cap= enter the result list unhighlighted; the +user would need to scroll to reach them, and most interactive sessions stay +within the top 20–50 results. + ** Stop / cleanup — =fzf_native_async_stop= 1. =score_abort = true= and =score_req_stop = true=, signal cond. From 347829e14d1699e414db086fc367c64ea6018d69 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 15:12:19 +0000 Subject: [PATCH 07/47] Update binary ubuntu-latest --- bin/Linux/fzf-native-module.so | Bin 57248 -> 65720 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index 1343630118027249343e6ad666bc9e662048fc28..6b329d8e0558af61d0697565e5c40f8f45d387e6 100755 GIT binary patch literal 65720 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 ZZVma){_<$Ce?;VdE!#?DuU+o^{{Rtnr-lFk From 304f046a798983626d1754b869f6da51559f42a0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 15:12:42 +0000 Subject: [PATCH 08/47] Update binary windows-latest --- bin/Windows/Release/fzf-native-module.dll | Bin 30720 -> 31232 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index 16a448659f7160eef3e20901807d774e20f49e25..3a65968c3851dff87e6988857ad52cbb9464a7e8 100644 GIT binary patch delta 10898 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 From 4940da9e910695c92fbe60e71c84ec3b10b30c93 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 15:13:05 +0000 Subject: [PATCH 09/47] Update binary macos-latest --- bin/Darwin/arm64/fzf-native-module.so | Bin 55048 -> 71816 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 4d22e0320b4be615328059eb59a7e99e12008c25..3da9ddfb351e5d3a3e3be75bc3731f82bf9faa74 100755 GIT binary patch delta 19186 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 From 250341ae17a85154edce2d838c0948afa2af1373 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 11:43:28 -0400 Subject: [PATCH 10/47] Fix c tests --- fzf-native-ctest.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index 728eb48..ec74161 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -376,7 +376,7 @@ static ScoredStr make_top(const char *str, int score) { static void test_cache_lookup_miss_on_empty(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr *out_top = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; @@ -388,7 +388,7 @@ static void test_cache_lookup_miss_on_empty(void) { static void test_cache_insert_then_lookup_hit(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[2] = { make_top("alpha", 42), make_top("beta", 17) }; cache_insert(&c, "fo", 1000, top, 2, NULL, 0); @@ -410,7 +410,7 @@ static void test_cache_insert_then_lookup_hit(void) { static void test_cache_lookup_miss_distinct_query(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[1] = { make_top("alpha", 42) }; cache_insert(&c, "fo", 100, top, 1, NULL, 0); @@ -427,7 +427,7 @@ static void test_cache_insert_updates_in_place(void) { than creating a duplicate. Verify count stays at 1 and the new data wins. */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr v1[1] = { make_top("alpha", 10) }; ScoredStr v2[2] = { make_top("alpha", 99), make_top("beta", 50) }; @@ -451,7 +451,7 @@ static void test_cache_lru_eviction_at_capacity(void) { /* Fill the cache, insert one more, verify the oldest entry is gone and all others remain. */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr one[1] = { make_top("x", 1) }; char qbuf[16]; @@ -493,7 +493,7 @@ static void test_cache_touch_on_hit(void) { /* Fill the cache; touch q0 (the oldest) so it becomes MRU; insert one more; verify q0 survived and q1 (now the LRU) was evicted. */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr one[1] = { make_top("x", 1) }; char qbuf[16]; @@ -526,7 +526,7 @@ static void test_cache_insert_zero_count(void) { /* Empty top[] is a legitimate "no matches" cache entry; verify it stores and looks up cleanly. */ Cache c; - cache_init(&c); + cache_init(&c, 0); cache_insert(&c, "nothing", 500, NULL, 0, NULL, 0); ScoredStr *out = (ScoredStr *)0xdeadbeef; @@ -587,7 +587,7 @@ static void test_subsumes_equal_strings(void) { static void test_cache_lookup_prefix_finds_subsumer(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top1[1] = { make_top("alpha", 10) }; uint32_t midx[3] = { 0, 5, 9 }; cache_insert(&c, "fo", 100, top1, 1, midx, 3); @@ -612,7 +612,7 @@ static void test_cache_lookup_prefix_picks_longest(void) { /* Two subsumers: "f" and "fo". Looking up "foo" should return "fo" (the longer one, since longer prefixes are more selective). */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top1[1] = { make_top("x", 1) }; uint32_t midx_f[10]; for (int i = 0; i < 10; i++) midx_f[i] = (uint32_t)i; uint32_t midx_fo[5]; for (int i = 0; i < 5; i++) midx_fo[i] = (uint32_t)i; @@ -636,7 +636,7 @@ static void test_cache_lookup_prefix_picks_longest(void) { static void test_cache_lookup_prefix_skips_or_query(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[1] = { make_top("x", 1) }; uint32_t midx[1] = { 0 }; cache_insert(&c, "fo", 100, top, 1, midx, 1); @@ -656,7 +656,7 @@ static void test_cache_lookup_prefix_skips_exact(void) { cache_lookup_prefix returns false — exact matches are the caller's responsibility (cache_lookup_exact). */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[1] = { make_top("x", 1) }; uint32_t midx[1] = { 0 }; cache_insert(&c, "fo", 100, top, 1, midx, 1); @@ -672,7 +672,7 @@ static void test_cache_lookup_prefix_skips_exact(void) { static void test_cache_lookup_prefix_miss_when_no_subsumer(void) { Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[1] = { make_top("x", 1) }; uint32_t midx[1] = { 0 }; cache_insert(&c, "ba", 100, top, 1, midx, 1); @@ -690,7 +690,7 @@ static void test_cache_insert_with_matched_idx(void) { /* Verify cache_insert + cache_lookup_exact round-trip the matched_idx array correctly. */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[2] = { make_top("alpha", 30), make_top("beta", 20) }; uint32_t midx[4] = { 7, 13, 21, 100 }; cache_insert(&c, "fo", 200, top, 2, midx, 4); @@ -732,7 +732,7 @@ static void test_cache_insert_or_query_no_idx(void) { /* OR queries (containing '|') must never store matched_idx since they can never serve as prefix-refinement sources. */ Cache c; - cache_init(&c); + cache_init(&c, 0); ScoredStr top[1] = { make_top("y", 5) }; uint32_t midx[3] = { 1, 2, 3 }; cache_insert(&c, "foo | bar", 100, top, 1, midx, 3); From bc8521e2018f7b81416867e46f2031c92d4947b9 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 12:00:24 -0400 Subject: [PATCH 11/47] Remove tests related to indices being returned --- fzf-native-test.el | 62 ++++++++-------------------------------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/fzf-native-test.el b/fzf-native-test.el index 5388bb4..3324629 100644 --- a/fzf-native-test.el +++ b/fzf-native-test.el @@ -4,20 +4,7 @@ (fzf-native-load-dyn) -(ert-deftest fzf-native-score-indices-order-test () - (let ((result (fzf-native-score "abcdefghi" "acef"))) - (should (= (nth 1 result) 0)) - (should (= (nth 2 result) 2)) - (should (= (nth 3 result) 4)) - (should (= (nth 4 result) 5)))) - -(ert-deftest score-with-default-slab-indices-order-test () - (let* ((slab (fzf-native-make-default-slab)) - (result (fzf-native-score "abcdefghi" "acef" slab))) - (should (= (nth 1 result) 0)) - (should (= (nth 2 result) 2)) - (should (= (nth 3 result) 4)) - (should (= (nth 4 result) 5)))) + (ert-deftest fzf-native-score-with-default-slab-test () "Test slab can be reused." @@ -25,16 +12,16 @@ (_result (fzf-native-score "abcdefghi" "acef" slab))) (should (equal (fzf-native-score "abcdefghi" "acef" slab) - '(78 0 2 4 5))) + '(78))) (should (equal (fzf-native-score "abc" "acef" slab) '(0))) (should (equal (fzf-native-score "zzzzzabc" "z" slab) - '(32 0))) + '(32))) (should (equal (fzf-native-score "sfsjoc" "jo" slab) - '(36 3 4))))) + '(36))))) (ert-deftest fzf-native-score-with-slab-test () "Test slab can be reused." @@ -42,16 +29,16 @@ (_result (fzf-native-score "abcdefghi" "acef" slab))) (should (equal (fzf-native-score "abcdefghi" "acef" slab) - '(78 0 2 4 5))) + '(78))) (should (equal (fzf-native-score "abc" "acef" slab) '(0))) (should (equal (fzf-native-score "zzzzzabc" "z" slab) - '(32 0))) + '(32))) (should (equal (fzf-native-score "sfsjoc" "jo" slab) - '(36 3 4))))) + '(36))))) (ert-deftest fzf-native-score-empty-query-test () (let ((result (fzf-native-score "abcdefghi" ""))) @@ -81,13 +68,13 @@ (let* ((len 4096) (str (concat (make-string len ?s) "d")) (result (fzf-native-score str "d"))) - (should (equal result `(16 ,len))))) + (should (equal result '(16))))) (ert-deftest fzf-native-score-very-long-str-test () (let* ((len 65536) (str (concat (make-string len ?s) "d")) (result (fzf-native-score str "d"))) - (should (equal result `(16 ,len))))) + (should (equal result '(16))))) (ert-deftest fzf-native-score-with-default-slab-benchmark-test () "Test scoring with slab is faster." @@ -118,32 +105,7 @@ (benchmark-run 10000 (fzf-native-score str query large-slab))))))) -(ert-deftest fzf-native-score-indices-multibyte-not-supported-test () - ;; Force `str' to be unambiguously multibyte regardless of the coding - ;; system used to load this file (eask may differ from an interactive - ;; session). Without the advice, the C module returns BYTE positions. - (let ((str (decode-coding-string - (encode-coding-string "ポケモン.txt" 'utf-8) 'utf-8))) - (should (multibyte-string-p str)) - (should - (equal (cdr (fzf-native-score str "txt")) - '(13 14 15))))) - -(ert-deftest fzf-native-score-indices-multibyte-support-through-advice-test () - ;; Force `str' to be multibyte (see `...not-supported-test' above). - ;; With the advice, byte positions are mapped back to character - ;; positions, so we expect (5 6 7) instead of (13 14 15). - (advice-add 'fzf-native-score :around #'fzf-native--fix-score-indices) - (unwind-protect - (let ((str (decode-coding-string - (encode-coding-string "ポケモン.txt" 'utf-8) 'utf-8))) - (should (multibyte-string-p str)) - (should - (equal (cdr (fzf-native-score str "txt")) - '(5 6 7)))) - ;; Always remove the advice, even if the assertions above failed. - ;; Otherwise the advice leaks into subsequent tests. - (advice-remove 'fzf-native-score #'fzf-native--fix-score-indices))) + (defun fzf-native-generate-random-string (length) "Generate a random string of LENGTH using alphanumeric characters." @@ -402,7 +364,3 @@ currently being scored. Stats are only written on completion, so (should done))) (fzf-native-async-stop handle)))) -(ert-deftest fzf-native-async-start-wrong-type-test () - "`fzf-native-async-start' signals on a non-string command." - (should-error (fzf-native-async-start 42) - :type 'wrong-type-argument)) From 937326879e33b3dcc647cd747d118a95fa475fda Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 12:03:00 -0400 Subject: [PATCH 12/47] Remove advice We're not returning indices anymore. --- fzf-native.el | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/fzf-native.el b/fzf-native.el index 95b59ed..c8c0762 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -143,23 +143,5 @@ module load." (require 'fzf-native-module)) (error "Fzf-Native will not work until `fzf-native-module' is compiled!")))) -(defun fzf-native--fix-score-indices (fn str &rest args) - "An around advice to fix score indices if STR is multibyte. -FN should be `fzf-native-score'." - (let ((score (apply fn str args))) - (if (or (null score) (not (multibyte-string-p str))) - score - ;; fzf-native makes score indices as byte position. - ;; But we want it as character position. - (let ((idx (cl-loop for i from 0 to (1- (length str)) - vconcat (make-vector (string-bytes (char-to-string (aref str i))) i)))) - (cons (car score) (mapcar (lambda (x) (aref idx x)) (cdr score))))))) - -;; Work around the lib's lack of support for multibyte chars. Add this -;; advice if you want accurate indices for multibyte chars. Don't add -;; this advice if you want better run time performance or you don't -;; need accurate indices for multibyte chars. -; e.g. (advice-add 'fzf-native-score :around #'fzf-native--fix-score-indices) - (provide 'fzf-native) ;;; fzf-native.el ends here From 9ca2b35d039d2576c6b37519c65b52d5f784cbba Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 12:05:19 -0400 Subject: [PATCH 13/47] Remove mention of advice --- README.org | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.org b/README.org index 8fd7416..428934d 100644 --- a/README.org +++ b/README.org @@ -85,17 +85,6 @@ Clone / download this repository and modify your ~load-path~: (fzf-native-load-dyn)) #+end_src -*** Multibyte character support - -Work around the lib's lack of support for multibyte chars. Add this -advice if you want accurate indices for multibyte chars. Don't add -this advice if you want better run time performance or you don't need -accurate indices for multibyte chars. - -#+begin_src emacs-lisp -(advice-add 'fzf-native-score :around #'fzf-native--fix-score-indices) -#+end_src - ** Use Cases [[https://github.com/jojojames/fussy][Fussy]]: ~fzf-native~ is used as From 13765c8736e8808148bd35e21649255522fe7e98 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 12:08:57 -0400 Subject: [PATCH 14/47] Update readme --- README.org | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.org b/README.org index 428934d..b86417a 100644 --- a/README.org +++ b/README.org @@ -59,6 +59,16 @@ Clone / download this repository and modify your ~load-path~: (add-to-list 'load-path (expand-file-name "/path/to/fzf-native/" user-emacs-directory)) #+end_src +*** use-package with :vc + +#+begin_src emacs-lisp +; Configuration that uses pre-built dynamic module. +(use-package fzf-native + :vc (:url "https://github.com/dangduc/fzf-native" :rev :newest) + :config + (fzf-native-load-dyn)) +#+end_src + *** Straight Examples #+begin_src emacs-lisp From cdab0acac14b07788acb0ed0b5f0b5012436b0ee Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 12:11:33 -0400 Subject: [PATCH 15/47] Require 29 --- fzf-native.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fzf-native.el b/fzf-native.el index c8c0762..380af1b 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -3,7 +3,7 @@ ;; Copyright 2021 Duc Dang ;; Author: Duc Dang ;; Version: 0.3 -;; Package-Requires: ((emacs "27.1")) +;; Package-Requires: ((emacs "29.1")) ;; Keywords: matching ;; Homepage: https://github.com/dangduc/fzf-native ;; SPDX-License-Identifier: GPL-3.0-or-later From b72933c3936595bfd3a0ae38e2e23df615eae42f Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 14:17:12 -0400 Subject: [PATCH 16/47] Extract $PATH from emacs and use shell set by emacs Before it was /bin/sh and used a minimal $PATH so we had to fully qualify binaries e.g. /opt/homebrew/bin/fd instead of just fd --- fzf-native-module.c | 91 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index ef6fc0f..0cf0581 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -1193,11 +1193,80 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, if (dir) env->copy_string_contents(env, args[1], dir, &dlen); } + /* Use shell-file-name / shell-command-switch so behaviour matches + shell-command (M-!) rather than hardcoding /bin/sh -c. */ + char *shell_prog = NULL, *shell_switch = NULL; + { + emacs_value sym = env->intern(env, "shell-file-name"); + emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) == emacs_funcall_exit_return && + !env->eq(env, v, Qnil)) { + ptrdiff_t slen = 0; + env->copy_string_contents(env, v, NULL, &slen); + if (slen > 1) { + shell_prog = malloc((size_t)slen); + if (shell_prog) env->copy_string_contents(env, v, shell_prog, &slen); + } + } else { + env->non_local_exit_clear(env); + } + if (!shell_prog) shell_prog = strdup("/bin/sh"); + } + { + emacs_value sym = env->intern(env, "shell-command-switch"); + emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) == emacs_funcall_exit_return && + !env->eq(env, v, Qnil)) { + ptrdiff_t slen = 0; + env->copy_string_contents(env, v, NULL, &slen); + if (slen > 1) { + shell_switch = malloc((size_t)slen); + if (shell_switch) env->copy_string_contents(env, v, shell_switch, &slen); + } + } else { + env->non_local_exit_clear(env); + } + if (!shell_switch) shell_switch = strdup("-c"); + } + + /* Build PATH from exec-path so the child shell can find binaries that + Emacs can find, even on macOS GUI launches with a minimal inherited PATH. */ + char *exec_path_str = NULL; + { + emacs_value sym = env->intern(env, "exec-path"); + emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + emacs_value sep = env->make_string(env, ":", 1); + emacs_value id = env->intern(env, "identity"); + emacs_value mc_fn = env->intern(env, "mapconcat"); + emacs_value mc_args[3] = {id, v, sep}; + if (env->non_local_exit_check(env) == emacs_funcall_exit_return) { + emacs_value joined = env->funcall(env, mc_fn, 3, mc_args); + if (env->non_local_exit_check(env) == emacs_funcall_exit_return) { + ptrdiff_t plen = 0; + env->copy_string_contents(env, joined, NULL, &plen); + if (plen > 1) { + exec_path_str = malloc((size_t)plen); + if (exec_path_str) + env->copy_string_contents(env, joined, exec_path_str, &plen); + } + } + } + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + } + + fzf_log("async_start: shell='%s' switch='%s' cmd='%s' dir='%s' PATH='%s'\n", + shell_prog, shell_switch, cmd, dir ? dir : "(nil)", + exec_path_str ? exec_path_str : "(inherited)"); + int pfd[2]; if (pipe(pfd) != 0) { fzf_log("async_start: pipe failed\n"); free(cmd); free(dir); + free(shell_prog); + free(shell_switch); + free(exec_path_str); return Qnil; } @@ -1208,6 +1277,9 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, close(pfd[1]); free(cmd); free(dir); + free(shell_prog); + free(shell_switch); + free(exec_path_str); return Qnil; } @@ -1217,11 +1289,28 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, close(pfd[1]); int dn = open("/dev/null", O_WRONLY); if (dn >= 0) { dup2(dn, STDERR_FILENO); close(dn); } + if (exec_path_str) { + const char *old = getenv("PATH"); + if (old && *old) { + size_t nlen = strlen(exec_path_str) + 1 + strlen(old) + 1; + char *new_path = malloc(nlen); + if (new_path) { + snprintf(new_path, nlen, "%s:%s", exec_path_str, old); + setenv("PATH", new_path, 1); + free(new_path); + } + } else { + setenv("PATH", exec_path_str, 1); + } + } if (dir) chdir(dir); - execl("/bin/sh", "sh", "-c", cmd, (char *)NULL); + execl(shell_prog, shell_prog, shell_switch, cmd, (char *)NULL); _exit(127); } close(pfd[1]); + free(shell_prog); + free(shell_switch); + free(exec_path_str); AsyncSession *s = calloc(1, sizeof *s); if (!s) { From 77e4bc676dbe1ec52e48e469b00967660d57d27b Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 18:20:18 +0000 Subject: [PATCH 17/47] Update binary ubuntu-latest --- bin/Linux/fzf-native-module.so | Bin 65720 -> 65880 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index 6b329d8e0558af61d0697565e5c40f8f45d387e6..faaa9acfdbd197c44917e560267b9fee06dce042 100755 GIT binary patch delta 13902 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 IT+DX=e?Vw+jsO4v delta 12591 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 Qtow`a#drKqB+qyHKb4?Ns{jB1 From fded535bd5adca0a6a11f022661bf1f7d53efe39 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 18:20:50 +0000 Subject: [PATCH 18/47] Update binary windows-latest --- bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 31232 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index 3a65968c3851dff87e6988857ad52cbb9464a7e8..7d1fded01701e6d4c5e58d860164b902fcb1046e 100644 GIT binary patch delta 25 ecmZqp!r1VIaf1XS6GQc8DaNcw5ZxA4B@X~}oCs?G delta 25 ecmZqp!r1VIaf1XS(+j`NQjA%VAi6E8N*(}(2npc; From 122000611505f4377b9b1e2870a516c3aac9ee77 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 18:23:40 +0000 Subject: [PATCH 19/47] Update binary macos-latest --- bin/Darwin/arm64/fzf-native-module.so | Bin 71816 -> 71944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 3da9ddfb351e5d3a3e3be75bc3731f82bf9faa74..a8f8f554ae351c931c83457b284d786f40cd2f20 100755 GIT binary patch delta 7858 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 G3-y2RrT38l From e897fc33ba4b8391047486f788726187b15ab05e Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 19:39:01 -0400 Subject: [PATCH 20/47] Remove --- architecture.org | 189 +++-------- fzf-native-ctest.c | 456 +------------------------- fzf-native-module.c | 765 ++++++++------------------------------------ 3 files changed, 189 insertions(+), 1221 deletions(-) diff --git a/architecture.org b/architecture.org index f1eff72..c6958c0 100644 --- a/architecture.org +++ b/architecture.org @@ -269,29 +269,25 @@ ready." ** AsyncSession layout #+begin_src - ┌──────────────────────────┐ - │ AsyncSession │ - ├──────────────────────────┤ - │ s->cands_top[][] │ ← two-level pointer table, under s->mu - │ s->count (atomic) │ ← candidate count; reader stores-release - │ s->arena │ ← bump allocator for candidate strings - │ s->gen (atomic) │ ← bumped on each score publish - │ s->stop (atomic) │ ← shutdown signal - │ s->max_line_length │ ← read-only after start; set from defcustom - ├──────────────────────────┤ - │ score_req_* │ ← protected by score_req_mu - │ filter, limit │ - │ cond, abort │ - │ current_filter/limit │ - │ refine_idx, delta_from │ ← LRU cache refinement params - ├──────────────────────────┤ - │ score_results[] │ ← protected by score_res_mu - │ score_count │ - │ last_filtered │ - │ last_total │ - ├──────────────────────────┤ - │ cache (LRU, 20 entries) │ ← per-session result cache - └──────────────────────────┘ + ┌─────────────────┐ + │ AsyncSession │ + ├─────────────────┤ + │ s->cands[] │ ← protected by s->mu + │ s->count │ ← protected by s->mu + │ s->arena │ ← protected by s->mu (string storage) + │ s->gen (atomic) │ ← published on score completion + │ s->stop (atomic)│ ← shutdown signal + ├─────────────────┤ + │ score_req_* │ ← protected by score_req_mu + │ filter, limit │ + │ cond, abort │ + │ current_* │ + ├─────────────────┤ + │ score_results[] │ ← protected by score_res_mu + │ score_count │ + │ last_filtered │ + │ last_total │ + └─────────────────┘ #+end_src ** Threads, locks, ownership @@ -299,9 +295,9 @@ ready." | Thread | Reads | Writes | |---------------------+--------------------------------+--------------------------------------------| | Main (Elisp) | =score_results= (under res mu) | =score_req_*= (under req mu); signals cond | -| Reader | child fp | =cands_top=, =s->count=, =s->arena= (under s->mu) | -| Scorer | =cands_top= directly (no lock; see safety note below), =score_req_*= | =score_results= (under res mu); =s->gen= | -| Scorer's sub-workers (transient) | their range slice of =cands_top= (read-only), =shared->pattern= (read-only) | per-worker result buffer | +| Reader | child fp | =s->cands=, =s->count=, =s->arena= (under s->mu) | +| Scorer | =s->cands= snapshot (under s->mu briefly), =score_req_*= | =score_results= (under res mu); =s->gen= | +| Scorer's sub-workers (transient) | their batch slice, =shared->pattern= (read-only) | their batch slice (compaction in-place) | Three locks, distinct purposes: @@ -320,36 +316,19 @@ Locks are never nested. The scoring thread takes them sequentially: #+begin_src while (read line from child fp) { strip ANSI escapes (in place) - apply max_line_length gate (exclude or truncate) - arena_strdup(line) ← arena owns the bytes - resolve block: hi = count >> SHIFT ← two-level pointer table - lo = count & MASK - alloc block on first use (2 MB, lazy) ← under s->mu - s->cands_top[hi][lo] = ptr ← under s->mu - atomic_store(count, count+1) + arena_strdup(line) ← arena owns the bytes + grow s->cands[] if at cap ← realloc, doubling + s->cands[s->count++] = ptr ← under s->mu } on EOF: just return; thread exits naturally. #+end_src -*Chunked candidate table:* rather than a single flat =char **= that must -be =realloc='d as it grows (causing VM-pressure stalls at tens of millions -of candidates), =cands_top= is a two-level pointer table. -=cands_top[CANDS_TOP_CAP]= holds up to 4096 block pointers; each block -holds =CANDS_BLOCK_SIZE= (256 K) candidate pointers (2 MB per block, allocated -lazily). The largest single allocation the reader ever makes is one 2 MB block, -regardless of pool size. - -*Max-line-length gate:* if =AsyncSession.max_line_length != 0=, lines -exceeding the cap are dropped (positive value) or truncated (negative value) -before =arena_strdup=. The value is read from the =fzf-async-max-line-length= -defcustom in =fzf_native_async_start= on the main thread before -=pthread_create=, so no locking is needed in the reader. - -*Arena:* candidates are written once and freed all at once at session end. -=arena_strdup= bumps a pointer into a 4 MB chunk; =arena_free= walks chunks -and frees them — O(chunks), not O(candidates). For 60 M candidates this is -the difference between instant teardown and seconds of individual =free()= -calls when ESC is pressed. +Why an arena: candidates are written once and freed all at once at +session end. =arena_strdup= bumps a pointer into a 4 MB chunk; +=arena_free= walks chunks and frees them — O(chunks), not +O(candidates). For 60 M candidates this is the difference between +"instant teardown" and "seconds of individual =free()= calls when ESC +is pressed." ** Scoring thread — =scoring_thread_fn= @@ -362,42 +341,24 @@ a filter or sets =score_req_stop=. if score_req_stop: exit thread steal score_req_filter (set score_current_filter under req mu) atomic_store(score_abort, false) - parse pattern once (on this thread — strtok is not thread-safe) - read pool_count under s->mu - build range array: N ranges of BATCH_SIZE candidates each (16 B/range) - spawn M worker pthreads, each claims ranges atomically + parse pattern (once, on this thread) + snapshot s->cands pointers (brief s->mu, malloc outside lock) + spawn N worker pthreads, each scores its batch pthread_join all workers ← runs HERE, never on main thread if score_abort: ← filter changed mid-run free, clear current_filter, loop - merge per-worker result buffers, counting_sort_scored, apply limit + compact survivors, counting_sort_scored, apply limit publish to score_results (under res mu), update stats atomic_fetch_add(s->gen, 1) #+end_src -*Range-based workers:* each worker receives an array of =AsyncScoringRange -{from, to}= structs (16 B each) and claims ranges atomically via -=atomic_fetch_sub= on a shared =remaining= counter. Workers resolve -candidate pointers directly from =cands_top= at score time — no pre-copied -snapshot array needed. This eliminates the O(N) snapshot malloc (447 MB for -55 M candidates) and replaces the old O(N) batch array (~1 GB) with an -O(N/BATCH_SIZE) range array (~430 KB). - -*Worker result buffers:* each worker grows its own =ScoredStr *results= -buffer only as matches are found, bounded by -=ceil(limit / num_workers + 1) × 4= entries. For limit=10000 and 9 workers -this is ≤ 71 KB per worker, capping total scoring memory at ≤ 640 KB -regardless of pool size. - -*cands_top read safety:* workers read entries at indices =i < pool_count= -without holding =s->mu=. This is safe because: (a) entries below pool_count -are written by the reader exactly once and never modified thereafter; -(b) the scoring thread captures =pool_count= under =s->mu= before spawning -workers, establishing a happens-before edge; (c) the store-release on -=s->count= by the reader and the load-acquire by the scoring thread provide -the necessary memory ordering. - -Workers check =score_abort= every 256 items inside their scoring loop. This -bounds ESC latency even at very large candidate counts. +Workers check =score_abort= every 256 items inside the batch loop, not +just at batch boundaries. This bounds ESC latency at very large +candidate counts. + +The batch loop also checks abort every 64 K items during the +*snapshot construction* before workers spawn, so a filter change +during a 25 M-item memcpy doesn't force a full pre-work pass. ** The "same-filter, don't abort" rule @@ -432,69 +393,19 @@ cross-thread publish signal.) ** Memory and string ownership -| Object | Owner | Lifetime | -|--------------------------------+----------------------------------+------------------------------------------------| -| Candidate strings | =s->arena= | Whole session — freed once at stop | -| =cands_top[i][j]= (pointers) | =s->mu=-protected writes; lock-free reads by workers | Blocks allocated on demand (2 MB each); freed at stop | -| =score_results[]= | =score_res_mu= | Replaced on each publish; final free at stop | -| =score_req_filter= | =score_req_mu= owns the slot | =strdup='d when stolen by scorer; freed before next steal | -| =score_current_filter= | Scoring thread owns; main thread reads under req mu | Cleared on every scoring exit path | -| =fzf_pattern_t= (scoring) | Scoring thread | Parsed once per request, freed after workers join | -| =fzf_pattern_t= (highlighting) | Main thread (in =fzf_native_async_candidates=) | Parsed per call for top-N highlight pass; freed before return | -| Per-worker =ScoredStr *= buffers | Each worker thread | Allocated when first match found; freed after merge | -| Cache =SharedIdx= (matched indices) | Reference-counted; shared between cache and score_req | Released when evicted or when refine slot is consumed | +| Object | Owner | Lifetime | +|-------------------------+----------------------------------+------------------------------------------------| +| Candidate strings | =s->arena= | Whole session — freed once at stop | +| =s->cands[]= (pointers) | =s->mu=-protected | Reallocated as it grows; freed at stop | +| =score_results[]= | =score_res_mu= | Replaced on each publish; final free at stop | +| =score_req_filter= | =score_req_mu= owns the slot | =strdup='d when stolen by scorer; freed before next steal | +| =score_current_filter= | Scoring thread owns; main thread reads under req mu | Cleared on every scoring exit path | +| =fzf_pattern_t= | Scoring thread | Parsed once per request, freed after workers join | The arena is the key invariant: every char* exposed in =s->cands= or =score_results= points into arena memory and is valid until the =AsyncSession= is destroyed. -** Result cache (=fzf_native_async_candidates=) - -Each session maintains a 20-entry LRU cache mapping =(filter, pool_gen)= -to the scored snapshot from that run, retaining the matched candidate indices -as a =SharedIdx= (ref-counted shared array). - -On each =fzf_native_async_candidates= call, three outcomes are possible: - -| Outcome | Condition | Action | -|---------------+---------------------------------------------------+-----------------------------------------| -| Exact fresh | Filter matches and pool hasn't grown | Return cached snapshot; no scoring queued | -| Exact stale or prefix hit | Filter matches (or is an extension of a cached filter) but pool grew | Return cached snapshot; queue scoring restricted to =matched_idx ∪ [pool_gen..now)= | -| Miss | No matching cache entry | Return current =score_results=; queue full scoring | - -The refinement path ("exact stale" and "prefix") avoids re-scoring the -entire pool on each keystroke during incremental narrowing. When typing -"foo" after "fo", the cache hit for "fo" constrains the next scoring run to -the ~fo~ matches plus any candidates that arrived after that run completed. - -** C-side match highlighting (=fzf_native_async_candidates=) - -After building the result list from the scored snapshot, =fzf_native_async_candidates= -applies =completions-common-part= face to matched character runs directly on -the Emacs string objects, before they are consed into the return value. - -Flow: - -1. Read =fzf-async-highlight= via =symbol-value=: - - =nil= → skip; no pattern parsed. - - =t= → =hl_cap = rcount= (highlight everything). - - Integer N → =hl_cap = N= (highlight top N only). -2. If =hl_cap > 0=: parse =filter_for_hilit= (a =strdup= of the filter taken - before its ownership transfers to the scoring thread) into an - =fzf_pattern_t=; allocate one shared =fzf_slab_t=. -3. For =snap[0..hl_cap-1]= (the highest-scoring candidates, which end up at - the front of the Emacs list): - - =fzf_get_positions(str, pattern, slab)= → =pos->data[]= descending. - - Iterate ascending; merge adjacent byte offsets into contiguous runs. - - =put-text-property(run_start, run_end+1, face, completions-common-part, str)= - once per run. =Qface= and =Qcompletions_common_part= are global refs - interned once at module init. -4. Free pattern, slab, and =filter_for_hilit=. - -Candidates for index > =hl_cap= enter the result list unhighlighted; the -user would need to scroll to reach them, and most interactive sessions stay -within the top 20–50 results. - ** Stop / cleanup — =fzf_native_async_stop= 1. =score_abort = true= and =score_req_stop = true=, signal cond. diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index ec74161..3983b8c 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -158,7 +158,7 @@ static void test_matches_qsort(void) { /* Abuse the str pointer as an order tag; counting_sort_scored never dereferences str, only copies it, so this is safe for tests. */ static ScoredStr make_scored(int score, size_t tag) { - ScoredStr s = {0}; + ScoredStr s; s.str = (char *)(uintptr_t)tag; s.score = score; return s; @@ -262,31 +262,24 @@ static void test_strip_ansi_bare_esc(void) { * async_reader (pipe-based; no Emacs runtime needed) * ===================================================================== */ -/* `cap` is no longer meaningful — chunked storage allocates blocks on - demand. Argument retained so existing callers don't change. */ static AsyncSession *make_async_session(FILE *fp, size_t cap) { - (void)cap; AsyncSession *s = calloc(1, sizeof *s); if (!s) return NULL; - s->fp = fp; + s->fp = fp; + s->cap = cap; + s->cands = calloc(cap, sizeof *s->cands); + if (!s->cands) { free(s); return NULL; } pthread_mutex_init(&s->mu, NULL); return s; } static void free_async_session(AsyncSession *s) { arena_free(&s->arena); - for (size_t i = 0; i < CANDS_TOP_CAP; i++) { - if (s->cands_top[i]) free(s->cands_top[i]); - } + free(s->cands); pthread_mutex_destroy(&s->mu); free(s); } -/* Test helper: read candidate i via the chunked accessor. */ -static char *cands_at(AsyncSession *s, size_t i) { - return s->cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK]; -} - static void test_async_reader_basic(void) { int pfd[2]; CHECK(pipe(pfd) == 0); @@ -303,9 +296,9 @@ static void test_async_reader_basic(void) { async_reader((void *)s); CHECK(s->count == 3); - CHECK(strcmp(cands_at(s, 0), "alpha") == 0); - CHECK(strcmp(cands_at(s, 1), "beta") == 0); - CHECK(strcmp(cands_at(s, 2), "gamma") == 0); + CHECK(strcmp(s->cands[0], "alpha") == 0); + CHECK(strcmp(s->cands[1], "beta") == 0); + CHECK(strcmp(s->cands[2], "gamma") == 0); free_async_session(s); } @@ -326,14 +319,13 @@ static void test_async_reader_ansi_stripping(void) { async_reader((void *)s); CHECK(s->count == 2); - CHECK(strcmp(cands_at(s, 0), "file.txt") == 0); - CHECK(strcmp(cands_at(s, 1), "plain.c") == 0); + CHECK(strcmp(s->cands[0], "file.txt") == 0); + CHECK(strcmp(s->cands[1], "plain.c") == 0); free_async_session(s); } static void test_async_reader_buffer_growth(void) { - /* Write a small batch of lines and verify they round-trip through the - chunked cands_top storage in order. All 32 fit within block 0. */ + /* Initial cap=4, write 32 lines — exercises the realloc doubling path. */ enum { NLINES = 32 }; int pfd[2]; CHECK(pipe(pfd) == 0); @@ -353,402 +345,11 @@ static void test_async_reader_buffer_growth(void) { char expected[32]; for (int i = 0; i < NLINES; i++) { snprintf(expected, sizeof expected, "line%d", i); - CHECK(strcmp(cands_at(s, i), expected) == 0); + CHECK(strcmp(s->cands[i], expected) == 0); } free_async_session(s); } -/* ===================================================================== - * Cache (per-session result cache, phase 1: exact-match) - * ===================================================================== - * The cache stores ScoredStr arrays whose .str pointers normally point - * into AsyncSession.arena. In these tests we use string literals as - * the .str values; the cache only memcpys the pointers, so this is - * safe — we never dereference them after teardown. - */ - -static ScoredStr make_top(const char *str, int score) { - ScoredStr s = {0}; - s.str = (char *)str; /* not freed by the cache */ - s.score = score; - return s; -} - -static void test_cache_lookup_miss_on_empty(void) { - Cache c; - cache_init(&c, 0); - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "foo", &out_top, &out_count, - &out_sidx, &out_gen) == false); - CHECK(out_top == NULL); - cache_destroy(&c); -} - -static void test_cache_insert_then_lookup_hit(void) { - Cache c; - cache_init(&c, 0); - ScoredStr top[2] = { make_top("alpha", 42), make_top("beta", 17) }; - - cache_insert(&c, "fo", 1000, top, 2, NULL, 0); - - ScoredStr *out = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "fo", &out, &out_count, &out_sidx, &out_gen) == true); - CHECK(out_count == 2); - CHECK(out_gen == 1000); - CHECK(out != NULL); - CHECK(out[0].score == 42); - CHECK(strcmp(out[0].str, "alpha") == 0); - CHECK(out[1].score == 17); - CHECK(strcmp(out[1].str, "beta") == 0); - free(out); - cache_destroy(&c); -} - -static void test_cache_lookup_miss_distinct_query(void) { - Cache c; - cache_init(&c, 0); - ScoredStr top[1] = { make_top("alpha", 42) }; - cache_insert(&c, "fo", 100, top, 1, NULL, 0); - - ScoredStr *out = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "bar", &out, &out_count, &out_sidx, &out_gen) == false); - CHECK(out == NULL); - cache_destroy(&c); -} - -static void test_cache_insert_updates_in_place(void) { - /* Re-inserting the same query overwrites the existing entry rather - than creating a duplicate. Verify count stays at 1 and the new - data wins. */ - Cache c; - cache_init(&c, 0); - ScoredStr v1[1] = { make_top("alpha", 10) }; - ScoredStr v2[2] = { make_top("alpha", 99), make_top("beta", 50) }; - - cache_insert(&c, "fo", 100, v1, 1, NULL, 0); - cache_insert(&c, "fo", 200, v2, 2, NULL, 0); - CHECK(c.count == 1); - - ScoredStr *out = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "fo", &out, &out_count, &out_sidx, &out_gen) == true); - CHECK(out_count == 2); - CHECK(out_gen == 200); - CHECK(out[0].score == 99); - CHECK(out[1].score == 50); - free(out); - cache_destroy(&c); -} - -static void test_cache_lru_eviction_at_capacity(void) { - /* Fill the cache, insert one more, verify the oldest entry is gone - and all others remain. */ - Cache c; - cache_init(&c, 0); - ScoredStr one[1] = { make_top("x", 1) }; - - char qbuf[16]; - for (size_t i = 0; i < ASYNC_CACHE_MAX_ENTRIES; i++) { - snprintf(qbuf, sizeof qbuf, "q%zu", i); - cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); - } - CHECK(c.count == ASYNC_CACHE_MAX_ENTRIES); - - /* Insert one more — should evict q0 (the LRU tail). */ - cache_insert(&c, "extra", 999, one, 1, NULL, 0); - CHECK(c.count == ASYNC_CACHE_MAX_ENTRIES); - - ScoredStr *out = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - - /* q0 is gone. */ - CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == false); - - /* q1 .. q(N-1) are still present. */ - for (size_t i = 1; i < ASYNC_CACHE_MAX_ENTRIES; i++) { - snprintf(qbuf, sizeof qbuf, "q%zu", i); - out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; - CHECK(cache_lookup_exact(&c, qbuf, &out, &out_count, &out_sidx, &out_gen) == true); - free(out); - } - - /* And the freshly inserted "extra" is present. */ - out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; - CHECK(cache_lookup_exact(&c, "extra", &out, &out_count, &out_sidx, &out_gen) == true); - CHECK(out_gen == 999); - free(out); - - cache_destroy(&c); -} - -static void test_cache_touch_on_hit(void) { - /* Fill the cache; touch q0 (the oldest) so it becomes MRU; insert - one more; verify q0 survived and q1 (now the LRU) was evicted. */ - Cache c; - cache_init(&c, 0); - ScoredStr one[1] = { make_top("x", 1) }; - - char qbuf[16]; - for (size_t i = 0; i < ASYNC_CACHE_MAX_ENTRIES; i++) { - snprintf(qbuf, sizeof qbuf, "q%zu", i); - cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); - } - - /* Touch q0 — moves it to head (MRU). */ - ScoredStr *out = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == true); - free(out); - - /* Now the LRU tail is q1. Insert one more; q1 should be evicted. */ - cache_insert(&c, "extra", 999, one, 1, NULL, 0); - - out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; - CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == true); - free(out); - - out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; - CHECK(cache_lookup_exact(&c, "q1", &out, &out_count, &out_sidx, &out_gen) == false); - - cache_destroy(&c); -} - -static void test_cache_insert_zero_count(void) { - /* Empty top[] is a legitimate "no matches" cache entry; verify it - stores and looks up cleanly. */ - Cache c; - cache_init(&c, 0); - cache_insert(&c, "nothing", 500, NULL, 0, NULL, 0); - - ScoredStr *out = (ScoredStr *)0xdeadbeef; - SharedIdx *out_sidx = NULL; - size_t out_count = 99, out_gen = 0; - CHECK(cache_lookup_exact(&c, "nothing", &out, &out_count, &out_sidx, &out_gen) == true); - CHECK(out == NULL); - CHECK(out_count == 0); - CHECK(out_gen == 500); - cache_destroy(&c); -} - -/* ===================================================================== - * Phase 2: subsumes() + cache_lookup_prefix() - * ===================================================================== */ - -static void test_subsumes_extending_term(void) { - /* "fo" → "foo" — extending a single term. */ - CHECK(subsumes("fo", "foo") == true); - CHECK(subsumes("foo", "fo") == false); /* shorter doesn't subsume longer's superset */ -} - -static void test_subsumes_adding_and_term(void) { - /* "fo" → "fo bar" — adding an AND term. Q starts with Q' textually. */ - CHECK(subsumes("fo", "fo bar") == true); -} - -static void test_subsumes_adding_negation(void) { - /* "fo" → "fo !bad" — adding a negation narrows. */ - CHECK(subsumes("fo", "fo !bad") == true); -} - -static void test_subsumes_or_in_prefix_rejected(void) { - /* Q' contains '|' → not safe to refine from. */ - CHECK(subsumes("fo|bar", "fo|bar baz") == false); -} - -static void test_subsumes_or_in_query_rejected(void) { - /* Q contains '|' → introduces OR; not safe to assume narrowing. */ - CHECK(subsumes("fo", "fo|bar") == false); -} - -static void test_subsumes_empty_prefix(void) { - /* Empty Q' subsumes anything (in practice we never cache empty). */ - CHECK(subsumes("", "anything") == true); -} - -static void test_subsumes_non_textual_prefix(void) { - /* "ba" is not a textual prefix of "foo". */ - CHECK(subsumes("ba", "foo") == false); -} - -static void test_subsumes_equal_strings(void) { - /* Q' == Q is trivially a prefix; subsumes() returns true. Callers - who want to skip exact matches do so explicitly. */ - CHECK(subsumes("fo", "fo") == true); -} - -static void test_cache_lookup_prefix_finds_subsumer(void) { - Cache c; - cache_init(&c, 0); - ScoredStr top1[1] = { make_top("alpha", 10) }; - uint32_t midx[3] = { 0, 5, 9 }; - cache_insert(&c, "fo", 100, top1, 1, midx, 3); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "foo", - &out_top, &out_count, - &out_sidx, &out_gen) == true); - CHECK(out_count == 1); - CHECK(out_sidx != NULL); - CHECK(out_sidx->count == 3); - CHECK(out_gen == 100); - CHECK(out_sidx->idx[0] == 0 && out_sidx->idx[1] == 5 && out_sidx->idx[2] == 9); - free(out_top); - shared_idx_release(out_sidx); - cache_destroy(&c); -} - -static void test_cache_lookup_prefix_picks_longest(void) { - /* Two subsumers: "f" and "fo". Looking up "foo" should return "fo" - (the longer one, since longer prefixes are more selective). */ - Cache c; - cache_init(&c, 0); - ScoredStr top1[1] = { make_top("x", 1) }; - uint32_t midx_f[10]; for (int i = 0; i < 10; i++) midx_f[i] = (uint32_t)i; - uint32_t midx_fo[5]; for (int i = 0; i < 5; i++) midx_fo[i] = (uint32_t)i; - - cache_insert(&c, "f", 50, top1, 1, midx_f, 10); - cache_insert(&c, "fo", 100, top1, 1, midx_fo, 5); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "foo", - &out_top, &out_count, - &out_sidx, &out_gen) == true); - CHECK(out_sidx != NULL); - CHECK(out_sidx->count == 5); /* came from "fo" entry, not "f" */ - CHECK(out_gen == 100); - free(out_top); - shared_idx_release(out_sidx); - cache_destroy(&c); -} - -static void test_cache_lookup_prefix_skips_or_query(void) { - Cache c; - cache_init(&c, 0); - ScoredStr top[1] = { make_top("x", 1) }; - uint32_t midx[1] = { 0 }; - cache_insert(&c, "fo", 100, top, 1, midx, 1); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - /* Q has '|' — refinement isn't safe. */ - CHECK(cache_lookup_prefix(&c, "fo|bar", - &out_top, &out_count, - &out_sidx, &out_gen) == false); - cache_destroy(&c); -} - -static void test_cache_lookup_prefix_skips_exact(void) { - /* If only an exact-match entry exists (no actual subsuming prefix), - cache_lookup_prefix returns false — exact matches are the - caller's responsibility (cache_lookup_exact). */ - Cache c; - cache_init(&c, 0); - ScoredStr top[1] = { make_top("x", 1) }; - uint32_t midx[1] = { 0 }; - cache_insert(&c, "fo", 100, top, 1, midx, 1); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "fo", - &out_top, &out_count, - &out_sidx, &out_gen) == false); - cache_destroy(&c); -} - -static void test_cache_lookup_prefix_miss_when_no_subsumer(void) { - Cache c; - cache_init(&c, 0); - ScoredStr top[1] = { make_top("x", 1) }; - uint32_t midx[1] = { 0 }; - cache_insert(&c, "ba", 100, top, 1, midx, 1); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "foo", - &out_top, &out_count, - &out_sidx, &out_gen) == false); - cache_destroy(&c); -} - -static void test_cache_insert_with_matched_idx(void) { - /* Verify cache_insert + cache_lookup_exact round-trip the matched_idx - array correctly. */ - Cache c; - cache_init(&c, 0); - ScoredStr top[2] = { make_top("alpha", 30), make_top("beta", 20) }; - uint32_t midx[4] = { 7, 13, 21, 100 }; - cache_insert(&c, "fo", 200, top, 2, midx, 4); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "fo", - &out_top, &out_count, - &out_sidx, &out_gen) == true); - CHECK(out_sidx != NULL); - CHECK(out_sidx->count == 4); - CHECK(out_sidx->idx[0] == 7); - CHECK(out_sidx->idx[1] == 13); - CHECK(out_sidx->idx[2] == 21); - CHECK(out_sidx->idx[3] == 100); - free(out_top); - shared_idx_release(out_sidx); - cache_destroy(&c); -} - -static void test_shared_idx_refcount_roundtrip(void) { - /* alloc → retain → release (first) → release (last) → freed. */ - uint32_t src[3] = { 10, 20, 30 }; - SharedIdx *p = shared_idx_alloc(src, 3); - CHECK(p != NULL); - CHECK(p->count == 3); - CHECK(p->idx[0] == 10 && p->idx[1] == 20 && p->idx[2] == 30); - - SharedIdx *q = shared_idx_retain(p); - CHECK(q == p); - - shared_idx_release(p); /* refcount 2 → 1; still live */ - CHECK(q->count == 3); /* accessible via q */ - shared_idx_release(q); /* refcount 1 → 0; freed */ -} - -static void test_cache_insert_or_query_no_idx(void) { - /* OR queries (containing '|') must never store matched_idx since they - can never serve as prefix-refinement sources. */ - Cache c; - cache_init(&c, 0); - ScoredStr top[1] = { make_top("y", 5) }; - uint32_t midx[3] = { 1, 2, 3 }; - cache_insert(&c, "foo | bar", 100, top, 1, midx, 3); - - ScoredStr *out_top = NULL; - SharedIdx *out_sidx = NULL; - size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_exact(&c, "foo | bar", - &out_top, &out_count, - &out_sidx, &out_gen) == true); - CHECK(out_count == 1); - CHECK(out_sidx == NULL); /* OR query: no matched_idx stored */ - free(out_top); - cache_destroy(&c); -} - /* ================================================================= */ int main(void) { @@ -780,37 +381,6 @@ int main(void) { RUN(test_async_reader_ansi_stripping); RUN(test_async_reader_buffer_growth); - printf("--- cache ---\n"); - RUN(test_cache_lookup_miss_on_empty); - RUN(test_cache_insert_then_lookup_hit); - RUN(test_cache_lookup_miss_distinct_query); - RUN(test_cache_insert_updates_in_place); - RUN(test_cache_lru_eviction_at_capacity); - RUN(test_cache_touch_on_hit); - RUN(test_cache_insert_zero_count); - - printf("--- subsumes ---\n"); - RUN(test_subsumes_extending_term); - RUN(test_subsumes_adding_and_term); - RUN(test_subsumes_adding_negation); - RUN(test_subsumes_or_in_prefix_rejected); - RUN(test_subsumes_or_in_query_rejected); - RUN(test_subsumes_empty_prefix); - RUN(test_subsumes_non_textual_prefix); - RUN(test_subsumes_equal_strings); - - printf("--- shared_idx ---\n"); - RUN(test_shared_idx_refcount_roundtrip); - - printf("--- cache_lookup_prefix ---\n"); - RUN(test_cache_lookup_prefix_finds_subsumer); - RUN(test_cache_lookup_prefix_picks_longest); - RUN(test_cache_lookup_prefix_skips_or_query); - RUN(test_cache_lookup_prefix_skips_exact); - RUN(test_cache_lookup_prefix_miss_when_no_subsumer); - RUN(test_cache_insert_with_matched_idx); - RUN(test_cache_insert_or_query_no_idx); - if (failed == 0) { printf("\nAll tests passed.\n"); return 0; diff --git a/fzf-native-module.c b/fzf-native-module.c index 0cf0581..d06d908 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -684,27 +684,10 @@ emacs_value fzf_native_make_slab(emacs_env *env, #if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) +#define ASYNC_INIT_CAP 4096 #define ASYNC_LINE_MAX 8192 #define ARENA_CHUNK_SIZE (4 * 1024 * 1024) /* 4 MB per chunk */ -/* Chunked candidate-pointer storage. - * - * The candidate-pointer array is split into fixed-size blocks owned by a - * top-level pointer table. Reader appends to the current block; when a - * block fills, it allocates the next one. No realloc ever moves pointer - * data, so the worst-case allocation the reader performs is a single - * block — predictable cost regardless of pool size. - * - * cands_top[] : CANDS_TOP_CAP slots × 8 B (~32 KB, fixed) - * cands_top[i] : CANDS_BLOCK_SIZE × 8 B (2 MB, on demand) - * - * Defaults: 256 K pointers per block, 4096 blocks → 1 G candidates max. - */ -#define CANDS_BLOCK_SHIFT 18 -#define CANDS_BLOCK_SIZE ((size_t)1 << CANDS_BLOCK_SHIFT) -#define CANDS_BLOCK_MASK (CANDS_BLOCK_SIZE - 1) -#define CANDS_TOP_CAP 4096 - /* Arena allocator: strings are packed into large chunks so freeing the entire candidate set is O(chunks) instead of O(candidates). */ typedef struct ArenaChunk { struct ArenaChunk *next; size_t used; char data[]; } ArenaChunk; @@ -746,284 +729,7 @@ static size_t async_strip_ansi(char *s, size_t len) { return w; } -/* idx is the candidate's position in the scoring-time snapshot. For - full-pool scoring this is the index in s->cands. For refinement - scoring (Phase 2) it is the index in s->cands of the candidate that - was looked up via the prefix entry's matched_idx, or in the delta - range. Same struct size as before on 64-bit (8 + 4 + 4 = 16). */ -typedef struct { char *str; int score; uint32_t idx; } ScoredStr; - -/* ===================================================================== - * Result cache (phase 1: exact-match, stale-while-revalidate) - * ===================================================================== - * Per-session LRU keyed by query string. Each entry holds a copy of - * the top-K ScoredStr published when the query was last scored, plus - * the candidate-pool size (s->count) at that moment. When dispatch - * sees an exact hit at the current pool size, it can return the - * cached result without scheduling new scoring. An older pool size - * means new candidates have arrived since: still return the cached - * result, but schedule a re-score so the next dispatch picks up the - * fresh data. - * - * ScoredStr.str points into AsyncSession.arena, which outlives every - * cache entry, so the cache stores those pointers directly without - * copying string bytes. Only the top[] array and the query string - * itself are owned by the cache. - * - * Reads happen at dispatch (Emacs main thread); writes happen at - * scoring-thread publish. Both serialize through `cache->mu`. - */ - -#define ASYNC_CACHE_MAX_ENTRIES 20 -/* Reference-counted immutable index array. Allocated once, retained by each - consumer in O(1) (atomic refcount bump under the cache mutex — no memcpy), - and freed when the last consumer releases it. Eliminates the multi-hundred- - millisecond dup_idx() stall that previously blocked the Emacs main thread - for large match sets (while-no-input cannot interrupt a blocking C call). */ -typedef struct { - _Atomic uint32_t refcount; - size_t count; - uint32_t idx[]; /* flexible array */ -} SharedIdx; - -static SharedIdx *shared_idx_alloc(const uint32_t *src, size_t n) { - if (!n || !src) return NULL; - SharedIdx *p = malloc(sizeof *p + n * sizeof *p->idx); - if (!p) return NULL; - atomic_init(&p->refcount, 1); - p->count = n; - memcpy(p->idx, src, n * sizeof *p->idx); - return p; -} -static SharedIdx *shared_idx_retain(SharedIdx *p) { - if (p) atomic_fetch_add_explicit(&p->refcount, 1, memory_order_relaxed); - return p; -} -static void shared_idx_release(SharedIdx *p) { - if (p && atomic_fetch_sub_explicit(&p->refcount, 1, memory_order_acq_rel) == 1) - free(p); -} - -typedef struct CacheEntry { - struct CacheEntry *prev; /* toward head (MRU); NULL at head */ - struct CacheEntry *next; /* toward tail (LRU); NULL at tail */ - char *query; /* strdup'd, owned */ - size_t pool_gen; /* s->count at score time */ - ScoredStr *top; /* malloc'd copy of published top-K */ - size_t top_count; - /* Phase 2: full match set (indices into s->cands at score time). - Reference-counted SharedIdx — retained without copying at lookup. */ - SharedIdx *matched_idx; -} CacheEntry; - -typedef struct { - pthread_mutex_t mu; - CacheEntry *head; /* most recently used */ - CacheEntry *tail; /* least recently used */ - size_t count; - size_t max_entries; /* set from fzf-async-cache-size at session start */ -} Cache; - -static void cache_init(Cache *c, size_t max_entries) { - pthread_mutex_init(&c->mu, NULL); - c->head = c->tail = NULL; - c->count = 0; - c->max_entries = max_entries ? max_entries : ASYNC_CACHE_MAX_ENTRIES; -} - -static void cache_free_entry(CacheEntry *e) { - if (!e) return; - free(e->query); - free(e->top); - shared_idx_release(e->matched_idx); - free(e); -} - -static void cache_destroy(Cache *c) { - CacheEntry *e = c->head; - while (e) { CacheEntry *n = e->next; cache_free_entry(e); e = n; } - c->head = c->tail = NULL; - c->count = 0; - pthread_mutex_destroy(&c->mu); -} - -/* Detach `e` from the list. Caller holds c->mu. */ -static void cache_unlink(Cache *c, CacheEntry *e) { - if (e->prev) e->prev->next = e->next; else c->head = e->next; - if (e->next) e->next->prev = e->prev; else c->tail = e->prev; - e->prev = e->next = NULL; -} - -/* Push `e` to the head (MRU). Caller holds c->mu. */ -static void cache_push_head(Cache *c, CacheEntry *e) { - e->prev = NULL; - e->next = c->head; - if (c->head) c->head->prev = e; - c->head = e; - if (!c->tail) c->tail = e; -} - -/* Helper: malloc a copy of a ScoredStr array. Returns NULL when n=0 - (caller treats as "no top-K"). On alloc failure returns NULL and - the caller decides whether that is fatal (it usually isn't — the - cache is best-effort). */ -static ScoredStr *dup_scored(const ScoredStr *src, size_t n) { - if (!n) return NULL; - ScoredStr *out = malloc(n * sizeof *out); - if (out && src) memcpy(out, src, n * sizeof *out); - return out; -} - -/* Find an entry by query and bump it to head. Caller holds c->mu. */ -static CacheEntry *cache_lookup_locked(Cache *c, const char *query) { - for (CacheEntry *e = c->head; e; e = e->next) { - if (strcmp(e->query, query) == 0) { - if (e != c->head) { - cache_unlink(c, e); - cache_push_head(c, e); - } - return e; - } - } - return NULL; -} - -/* Exact-match lookup. On hit, bumps LRU, copies the cached top[] out and - retains (O(1) refcount bump) the SharedIdx so the caller can release the - cache mutex before working with the data. Returns true on hit, false on - miss or top alloc failure (treated the same as a miss). */ -static bool cache_lookup_exact(Cache *c, const char *query, - ScoredStr **out_top, size_t *out_top_count, - SharedIdx **out_sidx, - size_t *out_pool_gen) { - pthread_mutex_lock(&c->mu); - CacheEntry *e = cache_lookup_locked(c, query); - if (!e) { pthread_mutex_unlock(&c->mu); return false; } - - ScoredStr *top_copy = dup_scored(e->top, e->top_count); - if (e->top_count && !top_copy) { pthread_mutex_unlock(&c->mu); return false; } - - /* O(1): just bump the refcount while holding the mutex. */ - SharedIdx *sidx = shared_idx_retain(e->matched_idx); - - *out_top = top_copy; - *out_top_count = e->top_count; - *out_sidx = sidx; - *out_pool_gen = e->pool_gen; - pthread_mutex_unlock(&c->mu); - return true; -} - -/* Subsumption rule (Phase 2 v1, conservative): - Q' subsumes Q (matches(Q) ⊆ matches(Q')) iff - - neither Q nor Q' contains '|' (no OR clauses), and - - Q starts with Q' as a textual prefix. - This catches: extending a term ("fo" → "foo"), adding new AND - terms ("fo" → "fo bar"), adding negations ("fo" → "fo !x"), and - anchors. It rejects every OR query — those fall through to - full-pool scoring. */ -static bool subsumes(const char *q_prime, const char *q) { - if (strchr(q_prime, '|') || strchr(q, '|')) return false; - size_t lp = strlen(q_prime); - if (lp == 0) return true; - size_t lq = strlen(q); - if (lq < lp) return false; - return memcmp(q, q_prime, lp) == 0; -} - -/* Walk the cache for the longest Q' (other than Q itself) that subsumes Q. - On hit, bumps the chosen entry to MRU, copies its top[] out and retains - (O(1)) the SharedIdx. Returns true on hit, false otherwise (miss or - top alloc failure). */ -static bool cache_lookup_prefix(Cache *c, const char *query, - ScoredStr **out_top, size_t *out_top_count, - SharedIdx **out_sidx, - size_t *out_pool_gen) { - pthread_mutex_lock(&c->mu); - CacheEntry *best = NULL; - size_t best_len = 0; - for (CacheEntry *e = c->head; e; e = e->next) { - if (strcmp(e->query, query) == 0) continue; /* skip exact */ - if (!subsumes(e->query, query)) continue; - size_t len = strlen(e->query); - if (len > best_len) { best = e; best_len = len; } - } - if (!best) { pthread_mutex_unlock(&c->mu); return false; } - - ScoredStr *top_copy = dup_scored(best->top, best->top_count); - if (best->top_count && !top_copy) { pthread_mutex_unlock(&c->mu); return false; } - - /* O(1): just bump the refcount while holding the mutex. */ - SharedIdx *sidx = shared_idx_retain(best->matched_idx); - - if (best != c->head) { cache_unlink(c, best); cache_push_head(c, best); } - - *out_top = top_copy; - *out_top_count = best->top_count; - *out_sidx = sidx; - *out_pool_gen = best->pool_gen; - pthread_mutex_unlock(&c->mu); - return true; -} - -/* Insert (or update) an entry for `query`. All allocations are done OUTSIDE - the mutex so the critical section is O(1) pointer swaps only — no memcpy - of potentially-huge index arrays while the Emacs main thread waits. - matched_idx is stored only for non-OR queries (OR queries can never be - prefix-refinement sources). Cache is best-effort: no-op on alloc failure. - Evicted entries are freed after the mutex is released. */ -static void cache_insert(Cache *c, const char *query, size_t pool_gen, - const ScoredStr *top, size_t top_count, - const uint32_t *matched_idx, size_t match_count) { - bool store_idx = (matched_idx && match_count && !strchr(query, '|')); - - /* Pre-allocate ALL resources OUTSIDE the mutex. */ - ScoredStr *new_top = dup_scored(top, top_count); - SharedIdx *new_sidx = store_idx ? shared_idx_alloc(matched_idx, match_count) : NULL; - CacheEntry *fresh = calloc(1, sizeof *fresh); - char *qdup = fresh ? strdup(query) : NULL; - if ((top_count && !new_top) || (fresh && !qdup)) { - free(new_top); shared_idx_release(new_sidx); free(fresh); free(qdup); return; - } - - pthread_mutex_lock(&c->mu); - - CacheEntry *existing = cache_lookup_locked(c, query); - if (existing) { - ScoredStr *old_top = existing->top; - SharedIdx *old_sidx = existing->matched_idx; - existing->top = new_top; - existing->top_count = new_top ? top_count : 0; - existing->matched_idx = new_sidx; - existing->pool_gen = pool_gen; - pthread_mutex_unlock(&c->mu); - free(old_top); shared_idx_release(old_sidx); - free(qdup); free(fresh); - return; - } - - if (!fresh) { - pthread_mutex_unlock(&c->mu); - free(new_top); shared_idx_release(new_sidx); - return; - } - fresh->query = qdup; - fresh->top = new_top; - fresh->top_count = new_top ? top_count : 0; - fresh->matched_idx = new_sidx; - fresh->pool_gen = pool_gen; - - CacheEntry *evicted = NULL; - if (c->count >= c->max_entries) { - evicted = c->tail; - if (evicted) { cache_unlink(c, evicted); c->count--; } - } - cache_push_head(c, fresh); - c->count++; - pthread_mutex_unlock(&c->mu); - - if (evicted) cache_free_entry(evicted); -} +typedef struct { char *str; int score; } ScoredStr; typedef struct { pthread_t reader; @@ -1032,9 +738,10 @@ typedef struct { _Atomic bool stop; pthread_mutex_t mu; - Arena arena; /* backing storage for all candidate strings */ - char **cands_top[CANDS_TOP_CAP]; /* chunked pointer array; entries are 2 MB blocks allocated on demand */ - _Atomic size_t count; /* written by reader (under mu); read by any thread */ + Arena arena; /* backing storage for all candidate strings */ + char **cands; + size_t count; + size_t cap; _Atomic int gen; size_t last_filtered; /* candidates matching last filter */ @@ -1052,20 +759,10 @@ typedef struct { char *score_current_filter; /* filter being actively scored (under score_req_mu) */ size_t score_current_limit; - /* Phase 2 refinement: when set, scoring scores against - score_req_refine_idx ∪ s->cands[score_req_refine_delta_from..) - instead of the full pool. refine_idx is a SharedIdx retained at - dispatch and released when the scoring thread steals it. */ - SharedIdx *score_req_refine_idx; /* may be NULL; ref-counted */ - size_t score_req_refine_delta_from; - bool score_req_has_refine; - pthread_mutex_t score_res_mu; ScoredStr *score_results; /* latest scored+sorted results */ size_t score_count; /* number of entries in score_results */ - Cache cache; /* per-session result cache */ - /* Read-only after session start; set from fzf-async-max-line-length defcustom. 0 = no limit. >0 = exclude lines longer than N chars. <0 = truncate to |N|. */ ptrdiff_t max_line_length; @@ -1095,35 +792,21 @@ static void *async_reader(void *arg) { char *dup = arena_strdup(&s->arena, line, len); if (!dup) continue; - /* Chunked append: figure out which block this index lands in, allocate - the block on first use, write the slot, publish via the count - store-release. The largest single allocation we ever do here is - one block (CANDS_BLOCK_SIZE * 8 B = 2 MB) — predictable cost - regardless of pool size, so memory-pressure stalls scale O(1). */ - size_t cur = atomic_load_explicit(&s->count, memory_order_relaxed); - size_t hi = cur >> CANDS_BLOCK_SHIFT; - size_t lo = cur & CANDS_BLOCK_MASK; - if (hi >= CANDS_TOP_CAP) continue; /* 1 G-candidate ceiling */ - - if (s->cands_top[hi] == NULL) { - char **blk = malloc(CANDS_BLOCK_SIZE * sizeof *blk); - if (!blk) continue; - fzf_log("async_reader: allocated cands block %zu (%zu entries, %zu MB)\n", - hi, (size_t)CANDS_BLOCK_SIZE, - (size_t)(CANDS_BLOCK_SIZE * sizeof *blk) / (1024 * 1024)); - pthread_mutex_lock(&s->mu); - s->cands_top[hi] = blk; - pthread_mutex_unlock(&s->mu); - } pthread_mutex_lock(&s->mu); - s->cands_top[hi][lo] = dup; - atomic_store_explicit(&s->count, cur + 1, memory_order_release); + if (s->count >= s->cap) { + size_t ncap = s->cap * 2; + fzf_log("async_reader: reallocating candidates %zu -> %zu\n", s->cap, ncap); + char **nc = realloc(s->cands, ncap * sizeof *nc); + if (!nc) { free(dup); pthread_mutex_unlock(&s->mu); continue; } + s->cands = nc; + s->cap = ncap; + } + s->cands[s->count++] = dup; pthread_mutex_unlock(&s->mu); atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); } fzf_log("async_reader EXIT: total=%zu gen=%d\n", - (size_t)atomic_load_explicit(&s->count, memory_order_relaxed), - (int)atomic_load_explicit(&s->gen, memory_order_relaxed)); + s->count, (int)atomic_load_explicit(&s->gen, memory_order_relaxed)); return NULL; } @@ -1132,8 +815,7 @@ static void *scoring_thread_fn(void *arg); /* defined after async_scoring_worke static void async_session_destroy(void *ptr) { AsyncSession *s = ptr; if (!s) return; - fzf_log("async_session_destroy: pid=%d count=%zu\n", (int)s->pid, - (size_t)atomic_load_explicit(&s->count, memory_order_relaxed)); + fzf_log("async_session_destroy: pid=%d count=%zu\n", (int)s->pid, s->count); /* Signal everything to stop simultaneously so scoring and reader wind down in parallel rather than sequentially. */ @@ -1144,9 +826,6 @@ static void async_session_destroy(void *ptr) { pthread_mutex_lock(&s->score_req_mu); free(s->score_req_filter); s->score_req_filter = NULL; - shared_idx_release(s->score_req_refine_idx); - s->score_req_refine_idx = NULL; - s->score_req_has_refine = false; s->score_req_stop = true; pthread_cond_signal(&s->score_req_cond); pthread_mutex_unlock(&s->score_req_mu); @@ -1162,14 +841,9 @@ static void async_session_destroy(void *ptr) { pthread_join(s->reader, NULL); if (s->fp) { fclose(s->fp); s->fp = NULL; } if (s->pid > 0) { waitpid(s->pid, NULL, 0); s->pid = -1; } - - cache_destroy(&s->cache); - pthread_mutex_lock(&s->mu); arena_free(&s->arena); - for (size_t i = 0; i < CANDS_TOP_CAP; i++) { - if (s->cands_top[i]) { free(s->cands_top[i]); s->cands_top[i] = NULL; } - } + free(s->cands); pthread_mutex_unlock(&s->mu); pthread_mutex_destroy(&s->mu); free(s); @@ -1331,27 +1005,12 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, s->pid = pid; s->fp = fdopen(pfd[0], "r"); - /* cands_top[] was zeroed by calloc(); blocks are allocated lazily by - the reader. No upfront pointer-array allocation. */ + s->cap = ASYNC_INIT_CAP; + s->cands = malloc(s->cap * sizeof *s->cands); pthread_mutex_init(&s->mu, NULL); pthread_mutex_init(&s->score_req_mu, NULL); pthread_cond_init(&s->score_req_cond, NULL); pthread_mutex_init(&s->score_res_mu, NULL); - { - size_t cache_size = ASYNC_CACHE_MAX_ENTRIES; - emacs_value sym = env->intern(env, "fzf-async-cache-size"); - emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); - if (env->non_local_exit_check(env) != emacs_funcall_exit_return) - env->non_local_exit_clear(env); - else if (!env->eq(env, val, Qnil)) { - intmax_t n = env->extract_integer(env, val); - if (env->non_local_exit_check(env) != emacs_funcall_exit_return) - env->non_local_exit_clear(env); - else if (n > 0) - cache_size = (size_t)n; - } - cache_init(&s->cache, cache_size); - } atomic_store(&s->gen, 0); atomic_store(&s->score_abort, false); @@ -1371,7 +1030,7 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, } } - if (!s->fp || + if (!s->fp || !s->cands || pthread_create(&s->reader, NULL, async_reader, s) != 0 || pthread_create(&s->score_thread, NULL, scoring_thread_fn, s) != 0) { async_session_destroy(s); @@ -1387,8 +1046,7 @@ fzf_native_async_stop(emacs_env *env, ptrdiff_t nargs, (void)nargs; AsyncSession *s = env->get_user_ptr(env, args[0]); if (s) { - fzf_log("async_stop: pid=%d total=%zu\n", (int)s->pid, - (size_t)atomic_load_explicit(&s->count, memory_order_relaxed)); + fzf_log("async_stop: pid=%d total=%zu\n", (int)s->pid, s->count); env->set_user_ptr(env, args[0], NULL); async_session_destroy(s); } @@ -1436,98 +1094,44 @@ static void counting_sort_scored(ScoredStr *xs, size_t n) { free(count); } -/* Lightweight work unit: a contiguous range of virtual indices to score. - No string data is embedded — workers resolve candidates from cands_top. */ -struct AsyncScoringRange { - size_t from; - size_t to; +struct AsyncScoringBatch { + unsigned len; + ScoredStr xs[BATCH_SIZE]; }; -/* Shared read-only context for all scoring workers. */ struct AsyncScoringShared { - AsyncSession *s; - fzf_pattern_t *pattern; - _Atomic bool *stop; - - /* Refine context: scattered previously-matched entries (bounded by LIMIT). - NULL for full-pool runs. */ - char **refine_snap; - uint32_t *refine_idx_snap; - size_t refine_count; - size_t refine_delta_from; - bool has_refine; - - /* Work distribution */ - struct AsyncScoringRange *ranges; + fzf_pattern_t *pattern; + struct AsyncScoringBatch *batches; _Atomic ssize_t remaining; - - /* Per-worker result cap (0 = unlimited). Set to a multiple of - ceil(limit/num_workers) to prevent memory exhaustion on broad queries. */ - size_t per_worker_limit; -}; - -/* Per-worker state: each spawned thread gets its own copy. */ -struct AsyncWorkerArgs { - struct AsyncScoringShared *shared; - ScoredStr *results; - size_t result_count; - size_t result_cap; + _Atomic bool *stop; /* points to session's score_abort */ }; static void *async_scoring_worker(void *ptr) { - struct AsyncWorkerArgs *args = ptr; - struct AsyncScoringShared *shared = args->shared; - AsyncSession *s = shared->s; - fzf_pattern_t *pattern = shared->pattern; - fzf_slab_t *slab = fzf_make_default_slab(); + struct AsyncScoringShared *shared = ptr; + fzf_slab_t *slab = fzf_make_default_slab(); + fzf_pattern_t *pattern = shared->pattern; - ssize_t ri; - while ((ri = atomic_fetch_sub_explicit(&shared->remaining, 1, + ssize_t bi; + while ((bi = atomic_fetch_sub_explicit(&shared->remaining, 1, memory_order_seq_cst) - 1) >= 0) { if (shared->stop && atomic_load_explicit(shared->stop, memory_order_relaxed)) break; - if (shared->per_worker_limit && args->result_count >= shared->per_worker_limit) - break; - - struct AsyncScoringRange *range = &shared->ranges[ri]; + struct AsyncScoringBatch *batch = shared->batches + bi; + unsigned n = 0; bool aborted = false; - for (size_t i = range->from; i < range->to; i++) { + for (unsigned i = 0; i < batch->len; i++) { if ((i & 0xFF) == 0 && shared->stop && atomic_load_explicit(shared->stop, memory_order_relaxed)) { aborted = true; break; } - - /* Resolve candidate: scattered refine entries first, then cands_top. */ - const char *str; - uint32_t orig_idx; - if (shared->has_refine && i < shared->refine_count) { - str = shared->refine_snap[i]; - orig_idx = shared->refine_idx_snap[i]; - } else { - size_t gi = shared->has_refine - ? shared->refine_delta_from + (i - shared->refine_count) - : i; - str = s->cands_top[gi >> CANDS_BLOCK_SHIFT][gi & CANDS_BLOCK_MASK]; - orig_idx = (uint32_t)gi; + int sc = pattern ? fzf_get_score(batch->xs[i].str, pattern, slab) : 1; + if (!pattern || sc > 0) { + batch->xs[n] = batch->xs[i]; + batch->xs[n++].score = sc; } - - int sc = pattern ? fzf_get_score(str, pattern, slab) : 1; - if (pattern && sc <= 0) continue; - - if (args->result_count >= args->result_cap) { - size_t newcap = args->result_cap ? args->result_cap * 2 : 64; - ScoredStr *nb = realloc(args->results, newcap * sizeof *nb); - if (!nb) { aborted = true; break; } - args->results = nb; - args->result_cap = newcap; - } - args->results[args->result_count++] = - (ScoredStr){ .str = (char *)str, .score = sc, .idx = orig_idx }; - - if (shared->per_worker_limit && args->result_count >= shared->per_worker_limit) - break; } if (aborted) break; + batch->len = n; } fzf_free_slab(slab); @@ -1549,13 +1153,6 @@ static void *scoring_thread_fn(void *arg) { char *filter = s->score_req_filter; /* steal ownership */ size_t limit = s->score_req_limit; s->score_req_filter = NULL; - /* Phase 2: also steal refine params (NULL if no refine). */ - bool has_refine = s->score_req_has_refine; - SharedIdx *refine_sidx = s->score_req_refine_idx; - size_t refine_delta_from = s->score_req_refine_delta_from; - s->score_req_refine_idx = NULL; - s->score_req_refine_delta_from = 0; - s->score_req_has_refine = false; /* Record what we're about to score so main thread can skip abort for same filter */ free(s->score_current_filter); s->score_current_filter = strdup(filter); @@ -1566,156 +1163,103 @@ static void *scoring_thread_fn(void *arg) { next dispatch that may have already set it again. */ atomic_store_explicit(&s->score_abort, false, memory_order_seq_cst); - /* Capture current pool count (brief lock). */ + /* Snapshot candidate count first (brief lock), then malloc outside lock, + then memcpy under lock. Keeps s->mu held only for the fast memcpy, + not for the potentially-slow malloc with tens of millions of candidates. */ pthread_mutex_lock(&s->mu); - size_t pool_count = atomic_load_explicit(&s->count, memory_order_relaxed); + size_t count = s->count; pthread_mutex_unlock(&s->mu); - /* Cap refine inputs against the current pool (defensive). */ - if (has_refine && refine_delta_from > pool_count) - refine_delta_from = pool_count; - size_t refine_count = has_refine ? refine_sidx->count : 0; - size_t delta_count = has_refine ? (pool_count - refine_delta_from) : 0; - size_t scount = has_refine ? (refine_count + delta_count) : pool_count; - - /* Allocate only the small arrays for scattered refine indices (bounded by - LIMIT ≤ 10000). Workers read full-pool and delta candidates directly - from cands_top — no O(pool_count) snapshot or batch array is needed. */ - char **refine_snap = refine_count ? malloc(refine_count * sizeof *refine_snap) : NULL; - uint32_t *refine_idx_snap = refine_count ? malloc(refine_count * sizeof *refine_idx_snap) : NULL; - if (refine_count && (!refine_snap || !refine_idx_snap)) { + char **snap = count ? malloc(count * sizeof *snap) : NULL; + if (!snap && count) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); continue; + free(filter); continue; } pthread_mutex_lock(&s->mu); - if (has_refine) { - for (size_t k = 0; k < refine_count; k++) { - uint32_t i = refine_sidx->idx[k]; - refine_snap[k] = (i < s->count) - ? s->cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK] - : ""; - refine_idx_snap[k] = i; - } - if (s->count < pool_count) pool_count = s->count; - if (delta_count > pool_count - refine_delta_from) - delta_count = pool_count - refine_delta_from; - scount = refine_count + delta_count; - } else { - if (s->count < pool_count) pool_count = scount = s->count; - } + if (s->count < count) count = s->count; /* cap if reader shrank (shouldn't happen) */ + if (snap) memcpy(snap, s->cands, count * sizeof *snap); pthread_mutex_unlock(&s->mu); - /* Build a range array — each entry is 16 bytes, no string pointers. - Workers resolve candidates from cands_top at score time. */ - size_t num_ranges = scount ? (scount + BATCH_SIZE - 1) / BATCH_SIZE : 0; - struct AsyncScoringRange *ranges = num_ranges ? malloc(num_ranges * sizeof *ranges) : NULL; - if (num_ranges && !ranges) { + /* Batch; check abort every 64 K items so a filter change is noticed quickly. */ + struct AsyncScoringBatch *batches = NULL; + size_t bi = 0, bcap = 0; + bool batch_ok = true; + for (size_t i = 0; i < count; i++) { + if ((i & 0xFFFF) == 0 && + atomic_load_explicit(&s->score_abort, memory_order_relaxed)) { + batch_ok = false; break; + } + if (!batches || (batches[bi].len >= BATCH_SIZE && ++bi >= bcap)) { + bcap = bcap ? bcap * 2 : 1; + struct AsyncScoringBatch *nb = realloc(batches, bcap * sizeof *nb); + if (!nb) { batch_ok = false; break; } + for (size_t k = bi; k < bcap; k++) nb[k].len = 0; + batches = nb; + } + batches[bi].xs[batches[bi].len].str = snap[i]; + batches[bi].xs[batches[bi].len].score = 0; + batches[bi].len++; + } + if (!batch_ok) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); continue; - } - for (size_t r = 0; r < num_ranges; r++) { - ranges[r].from = r * BATCH_SIZE; - ranges[r].to = (r + 1) * BATCH_SIZE <= scount ? (r + 1) * BATCH_SIZE : scount; + free(snap); free(filter); free(batches); continue; } - /* Reserve one core for Emacs's event loop and idle timers. */ - unsigned ncpu = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); - unsigned max_workers = ncpu > 1 ? ncpu - 1 : 1; + unsigned num_batches = count ? (unsigned)(bi + 1) : 0; + unsigned max_workers = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); size_t flen = strlen(filter); fzf_pattern_t *pattern = flen ? fzf_parse_pattern(CaseIgnore, false, filter, true) : NULL; bool has_pattern = (pattern != NULL); - /* Cap per-worker output to prevent memory exhaustion on broad/empty queries. - 4× slack so results don't cluster in a subset of workers. */ - unsigned num_workers_to_spawn = (unsigned)MIN(max_workers, num_ranges); - size_t per_worker_limit = (limit && num_workers_to_spawn) - ? (limit / num_workers_to_spawn + 1) * 4 : 0; - - struct AsyncScoringShared ws = { - .s = s, - .pattern = pattern, - .stop = &s->score_abort, - .refine_snap = refine_snap, - .refine_idx_snap = refine_idx_snap, - .refine_count = refine_count, - .refine_delta_from = refine_delta_from, - .has_refine = has_refine, - .ranges = ranges, - .remaining = (ssize_t)num_ranges, - .per_worker_limit = per_worker_limit, + struct AsyncScoringShared shared = { + .pattern = pattern, + .batches = batches, + .remaining = num_batches, + .stop = &s->score_abort, }; - struct AsyncWorkerArgs *worker_args = num_workers_to_spawn - ? malloc(num_workers_to_spawn * sizeof *worker_args) : NULL; - pthread_t *threads = num_workers_to_spawn - ? malloc(num_workers_to_spawn * sizeof *threads) : NULL; - unsigned num_workers = 0; - if (worker_args && threads && num_ranges) { - for (unsigned w = 0; w < num_workers_to_spawn; w++) - worker_args[w] = (struct AsyncWorkerArgs){ .shared = &ws }; - for (; num_workers < num_workers_to_spawn; num_workers++) - pthread_create(&threads[num_workers], NULL, async_scoring_worker, - &worker_args[num_workers]); + pthread_t *threads = malloc(max_workers * sizeof *threads); + unsigned num_workers = 0; + if (threads && num_batches) { + for (; num_workers < MIN(max_workers, num_batches); num_workers++) + pthread_create(threads + num_workers, NULL, async_scoring_worker, &shared); } for (unsigned i = 0; i < num_workers; i++) pthread_join(threads[i], NULL); free(threads); if (pattern) fzf_free_pattern(pattern); - /* If a different filter arrived while we were scoring, discard results. */ + /* If a different filter arrived while we were scoring, discard partial results. */ if (atomic_load_explicit(&s->score_abort, memory_order_relaxed)) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - if (worker_args) - for (unsigned w = 0; w < num_workers; w++) free(worker_args[w].results); - free(worker_args); free(ranges); - free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); + free(snap); free(batches); free(filter); continue; } - /* Merge per-worker result buffers into a single flat array. */ + /* Compact into flat array */ + size_t total = 0; + for (unsigned i = 0; i < num_batches; i++) total += batches[i].len; + + ScoredStr *flat = total ? malloc(total * sizeof *flat) : NULL; size_t pos = 0; - if (worker_args) - for (unsigned w = 0; w < num_workers; w++) pos += worker_args[w].result_count; - - ScoredStr *flat = pos ? malloc(pos * sizeof *flat) : NULL; - size_t flat_pos = 0; - if (flat && worker_args) { - for (unsigned w = 0; w < num_workers; w++) { - memcpy(flat + flat_pos, worker_args[w].results, - worker_args[w].result_count * sizeof *flat); - flat_pos += worker_args[w].result_count; - } - } - if (worker_args) - for (unsigned w = 0; w < num_workers; w++) free(worker_args[w].results); - free(worker_args); - free(ranges); - - /* Capture the full matched-index set BEFORE counting_sort - reorders flat[]. The cache stores this for prefix-refinement - scoring of extending queries (Phase 2). */ - uint32_t *matched_idx = NULL; - size_t match_count = pos; - if (pos) { - matched_idx = malloc(pos * sizeof *matched_idx); - if (matched_idx) { - for (size_t i = 0; i < pos; i++) matched_idx[i] = flat[i].idx; - } else { - match_count = 0; /* alloc failure: cache entry will lack the set */ + if (flat) { + for (unsigned i = 0; i < num_batches; i++) { + struct AsyncScoringBatch *b = batches + i; + for (unsigned j = 0; j < b->len; j++) + flat[pos++] = b->xs[j]; } + if (has_pattern && pos > 1) + counting_sort_scored(flat, pos); } - if (flat && has_pattern && pos > 1) - counting_sort_scored(flat, pos); - size_t emit = (limit && limit < pos) ? limit : pos; /* Clear active-filter marker before publishing results */ @@ -1728,25 +1272,16 @@ static void *scoring_thread_fn(void *arg) { s->score_results = flat; s->score_count = emit; s->last_filtered = pos; - s->last_total = pool_count; + s->last_total = count; pthread_mutex_unlock(&s->score_res_mu); - /* Cache what we just published, keyed by filter, tagged with the - candidate-pool size at score time. Subsequent dispatches with - the same filter at the same pool size skip scoring entirely. - matched_idx carries the full match set for prefix-refinement - scoring of extending queries. */ - cache_insert(&s->cache, filter, pool_count, flat, emit, - matched_idx, match_count); - free(matched_idx); - /* Increment gen so Elisp knows new results are available */ atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); - fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu refine=%d\n", - filter, pos, pool_count, emit, has_refine ? 1 : 0); + fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu\n", + filter, pos, count, emit); - free(refine_snap); free(refine_idx_snap); shared_idx_release(refine_sidx); free(filter); + free(snap); free(batches); free(filter); } fzf_log("scoring_thread EXIT\n"); @@ -1777,79 +1312,31 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, fzf_log("async_candidates: filter='%s' limit=%zu — dispatching to bg thread\n", filter, limit); - /* Read the current candidate-pool size so we can decide whether a - cache hit is fresh (same pool size) or stale (pool grew since). - count is _Atomic: no lock needed, so this call never blocks on - s->mu even when the reader is in the middle of a large realloc. */ - size_t current_count = atomic_load_explicit(&s->count, memory_order_acquire); - - /* Cache lookup. Three outcomes: - - exact fresh: return cached top, no scoring scheduled. - - exact stale: return cached top, schedule refine using this - entry's matched_idx + delta to current count. - - prefix match: return prefix entry's top (a superset of Q's - results), schedule refine using prefix entry's - matched_idx + delta. - - miss: return current score_results (today's behavior), - schedule full-pool scoring. */ - ScoredStr *snap = NULL; - size_t rcount = 0; - size_t entry_pool_gen = 0; - SharedIdx *entry_sidx = NULL; - - bool exact = cache_lookup_exact(&s->cache, filter, - &snap, &rcount, - &entry_sidx, - &entry_pool_gen); - bool prefix = false; - if (!exact) { - prefix = cache_lookup_prefix(&s->cache, filter, - &snap, &rcount, - &entry_sidx, - &entry_pool_gen); - } - bool exact_fresh = exact && entry_pool_gen == current_count; - bool can_refine = (exact || prefix) && entry_sidx != NULL; - - if (exact_fresh) { - /* Cache fully satisfied: nothing to score. */ - free(filter); - shared_idx_release(entry_sidx); - } else { - /* Schedule scoring. If we have a prefix or stale-exact hit, pass - the entry's matched_idx + pool_gen as refinement params so the - scoring thread restricts its work to {entry's matches ∪ delta - since entry was scored}. */ - pthread_mutex_lock(&s->score_req_mu); - bool filter_changed = !(s->score_current_filter && - strcmp(s->score_current_filter, filter) == 0 && - s->score_current_limit == limit); - if (filter_changed) - atomic_store_explicit(&s->score_abort, true, memory_order_seq_cst); - free(s->score_req_filter); - s->score_req_filter = filter; - s->score_req_limit = limit; - shared_idx_release(s->score_req_refine_idx); - s->score_req_refine_idx = can_refine ? entry_sidx : NULL; - s->score_req_refine_delta_from = can_refine ? entry_pool_gen : 0; - s->score_req_has_refine = can_refine; - pthread_cond_signal(&s->score_req_cond); - pthread_mutex_unlock(&s->score_req_mu); - /* If we didn't pass entry_sidx into the refine slot, release it. */ - if (!can_refine) shared_idx_release(entry_sidx); - } + /* Enqueue the new request. Only abort in-flight scoring if the filter + actually changed — same-filter timer re-triggers must not interrupt a + scoring run that is still working on the same query, which would cause + a livelock where scoring never completes on large candidate sets. */ + pthread_mutex_lock(&s->score_req_mu); + bool filter_changed = !(s->score_current_filter && + strcmp(s->score_current_filter, filter) == 0 && + s->score_current_limit == limit); + if (filter_changed) + atomic_store_explicit(&s->score_abort, true, memory_order_seq_cst); + free(s->score_req_filter); + s->score_req_filter = filter; /* scoring thread owns this now */ + s->score_req_limit = limit; + pthread_cond_signal(&s->score_req_cond); + pthread_mutex_unlock(&s->score_req_mu); - /* Cache miss (no exact, no prefix): fall back to score_results. */ - if (!exact && !prefix) { - pthread_mutex_lock(&s->score_res_mu); - rcount = s->score_count; - snap = rcount ? malloc(rcount * sizeof *snap) : NULL; - if (snap && s->score_results) - memcpy(snap, s->score_results, rcount * sizeof *snap); - else - rcount = 0; - pthread_mutex_unlock(&s->score_res_mu); - } + /* Copy latest scored results under lock so we can release quickly */ + pthread_mutex_lock(&s->score_res_mu); + size_t rcount = s->score_count; + ScoredStr *snap = rcount ? malloc(rcount * sizeof *snap) : NULL; + if (snap && s->score_results) + memcpy(snap, s->score_results, rcount * sizeof *snap); + else + rcount = 0; + pthread_mutex_unlock(&s->score_res_mu); /* Resolve C-side highlight cap from defcustoms fzf-async-highlight and fzf-async-highlight-max-candidates. Both are read via symbol-value so From 53fe7d9a39f6c937d44e7ea97e7215e454ef0d8d Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 23:41:13 +0000 Subject: [PATCH 21/47] Update binary ubuntu-latest --- bin/Linux/fzf-native-module.so | Bin 65880 -> 61680 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index faaa9acfdbd197c44917e560267b9fee06dce042..99acb083dab615f2337e8d055dc88c76ec7b145c 100755 GIT binary patch delta 20760 zcmZ`>349bq*6;2dgaDZd1Tx_a8Zdx?gyl%MOdx>{PB6+LD=I-Cf*eMYU_@XdnIO$D z8YpD(4y&@@!4(D61p-JoR1BbkE5ZOOJs=P~0OjcK|Ejw~r+2^l{kp5(zg|_ndiAR6 zRZY^z7KGNF3aLnx3W7_!_Gw(&PT-+*>C_&cRnO^!84FIG77>L~a)VCWySOMyOvzPHMe8z#{bhQUY-F^DY0L-ELX+<0>EjKD+TK0FWL8Hr~!9tvadJcws3o`(b>Qg|4N zNATpaLm?-{PHbante8b_76?ri?_tqlJ=nfbmpG0+6&A}@HgbtMloJ2ORw1V{!X<8E z(GjuY296%*?{l0t{ije?6c!_rm&2NVd5yBlg4Gf&hu-LcZs7bnAo_7c%J8VrkXu(h6qBm z5GN#_Mt2b+^hn6TKoXvA!)F=%Q*8J$lxr?T2|_o$f&`-4(@KMaU#uEf^x9^h0@vZ0~IRE+%)*~C-#1|I~PpJ?E@FX=!|BRzfrEmO^vWY-8GhAK-9Joj}SUxxA&_8BV3)g|=Q5Bgc7fjpXQ z2sDQ%%t1wD45fO3dVZQegs-&W8XDkToym&}yTkPOS)<{+2K*R<^~k3J>8o_c7J4KU zBc=w9ozel*80H#yz=nT@4iJBx4S&yQIKDx{f-sTB(1V6+sYZmBk{-`C2Dfg$PLFS; z$1kG+uIkrxdW{|lTa5;swgyHU^%NNOlW|b^(%_fDkN)4b!atFw9_+LAz-JUlwKedY z!C!0RKWy+<8vKxhhkOGsxLpr#U!d~Tv-mf4#uD_D!Vx@#=dI$TkfKMzC{)Dp@j5Wn z7@Mz9jyfRRZh$j*jfThC@HZtCqyiPEtIjn`mxdJ<&6+r)Kqx4hGHddLN#iFzIdjtZ z!l{qXoG@KL*7V6UDVkJl<9K?))FNTVXta(OB#@un(86z<$|C_|l-4b2?l& zX3#O5KTJ4d(6i#EVp@j^7Yw?H(;>p223_2m@+Be6m=M?*b?uvmU3-_V&11*)*u*Og zd@BtTf{B}S3qIU}=UQ-D zsm(Cfg5O)Of8^#_GA5YZh^JWa$1He(1s`U?ODy<37QEDglPkxA2k%9ejQb6oq6!O6 zew-OrTJV7;h`7>%lRsyMH5UBuCWv?ga1YTm1jY;_QrK!KU^qA69PZBQGqgc=^I2PXe;IiQ4_14e7 zcuR({IPvU63r-%f8Il8fiW(;EnIScxrwD)41QDkPbb|1qdXm3oPe7+AgB~;F2J{qR z=4waC*Z{qCUaY0{AupgOi9fO)h4ET&!*k=g1qSY+0!=xjze8jHlP!P8~zUmj#+S*&cfdT3qII_s}>xN zL4Bwrocd1*x3CDZ)?FLEO5R7Cg~{ zceLQi7W@tio@&AGwBYF$e29)y|8Y{aWF+Vr{GDsTJ6Z6t7Ch5}=UMR17JP~YH%KT_kC>a@l7QyE(ts`v73A(-%9cy2j55@o1g;{96$6 z9UY*+-yH4#(mD;b(SvNXoxi&+zq5^QZ=+jTX%8BVvI3yPZS<{A8wz;EMqjkiKilXN zHu_r&?V%nYv;aic*y!yxdb5pQXQNl!=r?Tiirch@tNexA0H+t)==nB!wvC=?qw{U_ zqc(a>J?+uQd_+B9*no{5WTX4o=fTKex&E4Hb4F0K{zxQLGu(@}&k$P`sFW=R!YbSt0aktTO*}#5Vk(8C6 zikiy4f#JZ_;B`$?Cuy2i6L#}s7Pu=_3SGonCw7vqJ%Fmx_3jogo@HaY^%Iw{rQQ0tnpKf#zq)7F1CU>m#N)jx(fka5YaTv8i>$SVKQQ67cr zA+$C!zP#E|E)eAlo3Md|Ku@Nn%KqN{o~vrn<55X^uYFZv%AAlU+C^fTn-H)5!LIch zCFQ=$M)XdWPON36y<0lhuf@fy5}M#>C7`CIYuP)!yGrfWvO~Q;jNWoV(?~wIqPdlr z1oc6-qR(texX7aV4i#6j(R~+*_puXwC$^vV3mBDA2@$fdSAy(s_TD8;`y?L9MoIM` z8=u-re45Qp9U%2w%|1-Mw_}mV?XQdwN$Ndr<*a&>H&k5U_RT0E`gPVdtxwv})nM=? zYv0JeQ3L3npP%fLBkS7lseMH>@a_bsTz;iU z=H=zIJ%xj4*b-sAvf_sEJ)*LW(_L-4aXG)zvDN51m5j##zWWyYaeDVwWf#O**|MU_ zI|HFPzPlTd^^Z|m_Zb~Ly-nLsl)+V4T|cF1*dj>ZVd^OrHKOl{g2S6eeL_~E`rcVh z3w8ymyAavF(KM(|u80CJ`tjyX47rJvWGj1v6+p}-gG^EldcdNOe)&I1iJ1xMp3T90 z@Kw0ew$WM7QN9pVa0y;|Lu8lWg%$AW>C{G2Yu;u!=aG;u2)UGNa8yG24pbm~Os|wH zYJXA;T8L10LUcYFJ`Z&gm_9-@I>9ElNJy;C*-B0J&YwfcOzwvU&;dSjqalgx`zRT; zVr(4cSzP&N)pmxK(k&KJE`!d9 zCgi%6hhYTD^n|f)WfGQdbW;w_XTQ#PN}6>qn>w@2;35oucG)Gd@aG)mG_9Ot-7p+1 zpI~=mkn`Ly!zpA;sps#4b^bRO>-ppx9`OcsG95#5*{%F4D|^)iKT!wIss|A3v&K<= z>o4l-bbTJAXk`4f^4!)I8DMY(b}Lh1WK#MtV~j}(m+YUad8>;$aaES@ca%2)yC*n> zYZ;37g^6#HW|D)C12W5&z)-OE@b$eoCkIRZ3-uO~a^<#-sK-fMZXu8Ts0;2AcvHHC zz*ZdI56~j%(-nN1I)rII74w@L99`bIPJ7$nuc9Q9pkV}^HrOCnv4fC z#$=4n80Gf=i`LN5v@^@k{X*$UvQIZMYP2M!TJ}Frt0Pe^Zf-PcGSuMMTvmsSMVSds zn#(zuU2Yn@^PcwlG=y?#8JTxl(@H-jR&+g)=AiGr00``|MaSK~DwWc{Px<1#w~TTq zxD_mK>dsT7z)PwEWxJIsn(b&_R?3K5Ekw6ve;Hrav6srq&XnqfqTAyyjHfC24i)k} z3){Y`W`i}STyvBM_*}f2m2yEX=apVnlaN<~Wra4Ctdzr9Df>;sGn}d&bmpgxHr0&@ zl&ro_?JAcr`?-KLBQP0|O%@x^5A1w%xF?Ky1}Iax%8Sc8&xTMHV@aDB+)(WCR2FkF zkDw{)CMe5NA5LgZN@+`Rl;1&>DSN3N6lj{D{wtuHLp;>pG=bVrE^12)uLr$1nSO%; zc^2_t#7I{(S`UW`0whbMjfMsQT}bq2Xl2(V$HJu%g0M3vf*(WOp(^FggQNH8JU9>6X zI|`3QjO`iAl?Z5Fg7?L37hE@$|f?w7@V{^4TUpM_myOu(`(FoEhYW$Q?w^E3;X8`Q4jGP0v zGO|S7fx)RO8!|=o&=fd*f{M*W^T#L!q!w}TRecGd2~|OZiHb`sYkU*j;>gc^wg_^i zRGW@h#&}F(FUcwl_Yb&9u{sfjvz0y2Kyfdsj8P}_8Krt2M@zO&CQxG#!c9WNLI|26 z|KDK|r7+mOO5J_F&>LknVs_bHk^B}}yyjNYBle~{ z!6ztvbJDIlyq+!?lKiZ4t@t&P5mI1xSd^pu#J4cn2RXBqAWbORc`&4B)d%@l@C8Zj z_QQW^Pvr`|q&ksBT8y9=!>?+`q0R$Xl=HirLnN1bzIS+EB)OFnx)Gm{#qW^{rJ@H> zRkl)1rt5t|qn1oHWcvqHqtAuIsho1nD4Ol>RhO+?&hGMOM%k%|qUKneNf&$Rx(|oL zx}r9FDTV1slXGwBHIL@eIFMFX{FAS*TiFxbcZ>E4VLKK46uJ?Dg=o2cx5$CxXeb3r z${^Kpz0iiAqx=d*W@5EUbi9NGGx!xc3ytyR=uX&#X>?b=uh$w5(LAUha!p=Rk0Xz( z>j)*FgwGKNSKw5ba)tY0vT}}Zxawe1G^{L@FNjbQEs^omvNO08Ez`1*sa(!XyDB?| zeucf>yAP=BABCk=xjO;Nv)ezwvyU&avhod;#sUtXkE~OcmEDH7h*OCYl>IHsuxh{w ze3-_wYVSTgKeiKjjKlpl(7<9;)U- z{R-_?>PWq*!44!UjFwL^BwuSumyx>_=+6ZRlWzq$zAN! zxCGCxv|ke#lVk?)FW=@s9aWhC8#Ce>CLK_j#7dyGJZmeOQE-pS%BkTbx~vJ&#@ry z8qIk)Z9&f%BvTHjR0l6!g(>+Sj7X`*J|WkpRHy6@K85v1efStLb7#is#@eg;s1biN zFKqu4TE5*1l&Ps7@)mXP#(Dk)vUJXW{z(4q35V5;`?TDng)7UnwALP{#gFwSM8wk` zDl0c-<&0xyq zYBF1B@L|Dqj&tpj{ps+D-7U1zKO>7gr%L~9R&+dNe@eAubFWa@Um~WL-fH9UjsbJ} zqF$J**W|LDu#DPSSm5`e4>h5odVLRLJ(hhhrBk)#hl@Ipt8?``*&)MJ_RZ(=zL*N0 zn$V_@ZXa%MWVb(0%)yL~&h}+RWcy=6S&#m$Jr{Jb9o|2nbGL$xZjZb-o%=?zk|n29 z=lG|GL7%6w$&i~egZO<}vRllHaQi3o%`iy1@QsFBP@E@Y#w^h99_Hn`{k{6oKBoT5 zY=t&*)SFA}ZlyTSBg2Oy*F(txeRr)o0-SYaSyMz~2)s}5y(kmKaKfcAvUoo@rIGjJ z1bW;1f$via9P821!Xc#t28_cnq7F-J4d zrAxPwbUE}&9-BNcReJMl+^F3x9r~JmIdF+oc7%-^lr1G5VXFrvcz7!}N6KZtxYU{> zSV{xELv?u$0BlvFhDM{tR{}l&Ao>mBK~;rAY~7GJsrW0le@Iv9)MEC>kOXPS5!NQNU1$;PknPCq*ur5i zHK>*qWVT8U{|biM5&fxvd>LSJ`{IL}V7L|Pr^w=qEba++akEizWxP4Ft7nN~6MHLZjp~#5Ax~yoPyHJdqclhsD*S6*W`OQK`LI{2<|*N0K|S zp1(65q)N)aFRnvPGKntx=H;r|7kcA+ z4Ixf~YaIMwT&xGrATP$)&gf#bQE(HCFu!6qTQx9-y)-PTF|C*w%j?gx&xa+1LdV~* z--h*z(RfqXiwbaYj_HGoq|aD}9MkbJFi(4)V_0!c;;N8eh87QZ`=`TK^cAxbx&3`B zQCz>gENTM`78>MMaZ^;m8MGD>5y8teA=-uob#6)v^0h>>B#D`rdX z?bz1^7YZ0%ZTklXz@&zA_0t07?|l>i&&7S>v|N2g)R*<7(=Cx%_8ls_jyj0CohbW{ z|F0}vuPeb%3`2!?l%aj2Q~uu5^+L4jYOIn5!{7TM6Wc>2{k=!(bT&Ja9p8)=>0ILn zpCslS9yf-5uh4%0YK&D}_TxCAd)gNvE8RMz@u0IAh}CFwCd!lf-vbtMvik1#;BXO|fSS^wg?~Z| zN>+rLLSpXC%A>#i+#!_Gh-5j{^Hh<4UObLC*y`0^h{?Z=W-_M737w(|4XTo}@~H=E z7xFR{_4%wRnF~MQS9bp1TSn{S?13fuCR)rOl@H}f*^hf=V>~gKUqjC@Y<&>Cq)Wp{ z%H}zRqC4fXdAUMSTat{9bImVPmoKSvAO@+rISm@k1_DOC*B=o2cnD+r>kZRBF)m3{Eu|7iI7%8Xv zIi*l+H%G#HL5^M!BuH27#;LN6-K<<>sW~0TKLb=9#K)W_ z2bOY@ZzA#H!cVV5x5D6ffaVDh{a>}O_SGX!$-XKHl7D) zX=5#KqrtB*Nj>-Ok2uo^Ks9Qz!%x8Q@QL4ANqQ}v3T z$mF}LdapsLvyjj?T>n7NaQr<|O0~Kk^(wo|t4mrad$&VIL2U;vG&mFyU8dhH``aag z7rj%PqfwmrX`c=&O%;kWj9ElBrIsTTm*GWN=QVXJ7Z0u*%>-vwO0`=#MMtHp>OnNP z!$a5EYA7+mX6GX2j>#VNDI~M~MPU+H`GK(#V(6|;(ObE6r{)iYE&Rqtmw=g|o27OMUZnx@iR#1H*Fn8&9ACLNvY*w=+4`Xr$yvIu`tDpFV-KEWa; za5VsS`;GmUqpi;?uB5VHkVgiN6|_70FoViN)>fH1}C6ODI^fX}_jz z-K-|50z4QRqne+1OIc(QIuEvdxw)_9`8iguJvnS?NsROP2H-j!oYtVeWjg3+0M6FI zkOsvc(!qZ;00-#crwzb6bZ~tGFkA=Udd4c}%lC3Qdo`+3siWkk&uV&OX~sI`{9Yn7R8zsbX8ziFva_By}qr^V}rPON^>!$=;q@`1#bGHknrn5eaKb7uX&wg8cpVV&y%k;UW^Xq(#gAHs+ZA?4cL`Esc4PxhmF6#cSFA zimpBF_`lcmd~__b9dmVG3)j$2KO;wc=dNj3QK-kY!zONY91 zHEXxL^?;w=v1Q}uJhcL4YP<>Wfsf8jYW06JIn`;aS>f^?v2%FdIXL@9{rF8%?X;SG zynKNqzQqQ9=gn zkR<-MHTJ-YwB}12W~v!)SG~UCpd^X^teWw1cQN+4H)(JBPFDU?-;DzG?n<`ll}}^W zzd;q#-yGEEja46dv&#EwQKP%V#g0`k5(hSQczf!PFR;JX zKG*plZDDv{RG@pf2E`9PSJnGoAhqw2yBwp-N3ykfFS1qdX7#+ASSNs$|B2T{s;P_0 zkCVIAuBbXE?FR0u$%}9d`5HTc)`q#>>nzQGfu+AU-s4BLHKE z%pERR41T#$Yy8HE6+f@-#fF;Yy#OCe9fr#W_`!ng$O`j;(9dD<+^vSH14~HeliM4k z7I;T=ztRj9(d2@o)EYNLto{2-QjaB{sj0i!an_}JU;&z2-VYx}_?O7thfSB#=j10%D12t-#H1-x zAD=QEPZ8@??PFh8$5h>@{!9#|2CH`LnjGSx?*!1l2{@m?{Wa1RGc;`t;!4ETh-;tH zv}1?^h<`y`JxkN#aVn@qOux0&72#qMaeT3+RU%GC{2}49H7yh#VlHINMO=w^F5-eR z)Qi{&%W#Im>CZ(X83GlofB=Z&U)Ho*#C3?9hC#AdHEk5)ir3Hp;>tH62;xAcrnztn zC9Kl4yAkIi9!v3BP1}mNa-*ivKT}k1(zHp4Bi$6l=_er&;)|``Ohd0Bvl-~Wf*!_m40r*wVg}+V!c+I8rlBexlJQ=I z=a(m0`+YrItt_~mA`%w1xM}{bq zb*ZMkC9&3Dw(q_n-W|0eJv*v8J>-N4k%u%CS*BvCc&4M6yqrC8pikr)B#&czY%HR| zj_mUT?IP;{z5No-y#%#wl1a^HM&*V)E>hzx>GMY617M}<3fAxQ!NU_#-y)Fj!)OOW zqXN{kL>e09S{RZU6<-z_(pijhWpb7Ak@yh2)vst;G*~H6`F+5?c!iz&ymQark*q`Z zb=a#gG|Ks+1dYEC0%iI`v!W80gk_LQxJ<*MD#ehbP&Q$I%ML@EgiHvDS|i>cwLzSQ zI0Jb?CaU1`=bxW z+lRjfNd1UCc5rC8isXQ;Y~8`3je9|=%1<<{J-d9M1#^Ct99{~fXd4^)Rrkg;#VRqy zTC$A?Td<0+dWDC={0erk17AHBJ{`#^->{ywL&LWsIsG`BU)v}AcO>yOlTT`gcg}=q zCZEu>E|5|fPS!hWH<>RDa~|?Ckyj$J`CoNrxrgGzHvlR+$z~tw*;+;NIA&@KDv3ZF z_zhs{$*Nt4qMESkc52VORmb-#hfXXrRz7m%D`;CTk#+z{Gh;7(l8 zAb11?0EOj7Mk(0~!dr&8s|-9D7Y`J6>XES7z&GH2m;#@=h{@ri4%F%YnTxL?=pks} zPF!76$l~w6;iAaM$TKqd#6@N+2q5B<3TsYt&ienKZO1nL7<&i(1q;{H<9coz)Iy=R zf!9u9=YH%Q>E@L*7nbZ~t$*t5d5Gsa1k+yiGMFEn%_VF-Wg>b^|Ck`+|4hvf2bsC} zR}X|}!TfTNi7Q4urs0`5U091kY*$Pv6VhW^tt9$a-}r{O{so=w{ChXw(YxI_R6(Dr zuz!T(8t`~bjuvq-Sa#_f6zQk+x-j>7fWks!Q+goxe|;Jw!@%v|U3q{?c_tZk)*6a2 z>wLl}W&fZ?iNSCAG6$EzYw+8@;pyk@xPZ z(Qcxx?;+4H=_Ft)8;7q9x%k(O2GKk2@8Bct2uZrm%u)Y3&(wQle&uI77w#B;|H9uIy;o$pQ=eS@CsYU zvJ5=eh7Si$9kzdvX`I2o=rma=f;kf>uvO<{#bW-xNFt`JFj(VlRli|YUCApDUeP1r zeO`74y;F`DJoc|M{bbacXRGrfm7359!jRDRuTHrPymqeMR7**Zy8$O<(T9U5n0-k$ z@KhV#pKZJln?2gdJ!Tv8hYdX0R%)_=PqE>c(-&jo78L{N0PcAE0+t@};3-=ZO<$PMSJtLebHojrLx zKWL7BeDci6vj99flcij45u<~J6Q7(lb>`#apMG-Iw8^trXJ6t@z|wB^XdZu;j+^mQbJ(1l z-6PFScKIr{>1JQ&2A#?L%%79}d9z2fxq+JTB$jroM|25!RtRS7V$a-4ZhTw^YrkNd zZuK>(V_&deZ&QIUSWm5Q;}be(udU)>Q_d2_OaF@(%k8{9%8?0eIm^byZX>e zJ;gzds}JeCNlZN5;-w{GLd!Y>i7<5C77Sfp+9P&rY<{cfiI@Hq`$j&d>Lr_rtl#za F{|8Y%><<6{ delta 26283 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 Date: Sat, 9 May 2026 23:42:11 +0000 Subject: [PATCH 22/47] Update binary macos-latest --- bin/Darwin/arm64/fzf-native-module.so | Bin 71944 -> 55384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index a8f8f554ae351c931c83457b284d786f40cd2f20..8d443aa399c5309349c41ae7a0a71be64a223379 100755 GIT binary patch delta 11745 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 delta 15232 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 From 4b5c999c3f8ac7d0f0cf1e8147fd62a414bb5837 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 23:43:43 +0000 Subject: [PATCH 23/47] Update binary windows-latest --- bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 31232 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index 7d1fded01701e6d4c5e58d860164b902fcb1046e..b08c17721b18b3e3baed7463f38d5074ba502036 100644 GIT binary patch delta 25 ecmZqp!r1VIaf1XS)7oR3r5Lj!L3CSGl{^56Rtj4H delta 25 ecmZqp!r1VIaf1XS6GQc8DaNcw5ZxA4B@X~}oCs?G From e781893afaa17fdbe9f147ee35b610d0d13ffb80 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 19:55:36 -0400 Subject: [PATCH 24/47] Try action again --- .github/workflows/cmake-binaries.yaml | 58 ++++++++++++++++++--------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cmake-binaries.yaml b/.github/workflows/cmake-binaries.yaml index 0285e67..32a105b 100644 --- a/.github/workflows/cmake-binaries.yaml +++ b/.github/workflows/cmake-binaries.yaml @@ -6,25 +6,15 @@ on: branches: ["develop"] paths: ["**.c", "**.h"] -# Make sure to build each platform binary one-at-a-time. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: - REPO: "dangduc/fzf-native" - SOURCE_BRANCH: ${{ github.ref }} - TARGET_BRANCH: ${{ github.ref }} - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: build: - # The CMake configure and build commands are platform agnostic and - # should work equally well on Windows or Mac. You can convert - # this to a matrix build if you need cross-platform coverage. - # See: - # https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -32,26 +22,54 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 - - name: Pull branch changes - run: | - git pull + - name: Clean stale binaries + shell: bash + run: rm -rf bin - name: Configure CMake - # Configure CMake in a 'build' subdirectory. run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build - # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --verbose --config ${{env.BUILD_TYPE}} + - name: Upload binaries + uses: actions/upload-artifact@v4 + with: + name: bin-${{ matrix.os }} + path: bin/ + if-no-files-found: error + retention-days: 1 + + commit: + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download all binaries + uses: actions/download-artifact@v4 + with: + path: bin-artifacts + pattern: bin-* + merge-multiple: true + + - name: Stage binaries + run: | + mkdir -p bin + cp -R bin-artifacts/. bin/ + rm -rf bin-artifacts + - name: Commit changes uses: EndBug/add-and-commit@v9 with: author_name: github-actions author_email: github-actions@github.com - message: Update binary ${{ matrix.os }} - add: "bin/**" \ No newline at end of file + message: Update binaries for all platforms + add: "bin/**" From eaeffe2db22a43bfbf2e00b6c7b3e9050ce1cf68 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 9 May 2026 23:56:51 +0000 Subject: [PATCH 25/47] Update binaries for all platforms --- bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 31232 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index b08c17721b18b3e3baed7463f38d5074ba502036..93b2ee83793be813ee3d6ca8d59509103480305a 100644 GIT binary patch delta 25 ecmZqp!r1VIaf1XS)Ao~_r5Lj!L3CSGl{^57ZVGh( delta 25 ecmZqp!r1VIaf1XS)7oR3r5Lj!L3CSGl{^56Rtj4H From cba91ff8988fe199056a8bb74b027c9108b79475 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 20:10:20 -0400 Subject: [PATCH 26/47] Add test message --- fzf-native-module.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fzf-native-module.c b/fzf-native-module.c index d06d908..9bbf664 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -1593,5 +1593,10 @@ int emacs_module_init(struct emacs_runtime *rt) { Qzero = env->make_global_ref(env, env->make_integer(env, 0)); Qone = env->make_global_ref(env, env->make_integer(env, 1)); + /* Unconditional load marker — visible in *Messages* after rebuild + reload. */ + emacs_value load_msg = env->make_string(env, "fzf-native: module loaded", + (ptrdiff_t)sizeof("fzf-native: module loaded") - 1); + env->funcall(env, Fmessage, 1, &load_msg); + return 0; } From 9e3707438683018f3391c50e93d657d2c00c7ad2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 00:11:14 +0000 Subject: [PATCH 27/47] Update binaries for all platforms --- bin/Darwin/arm64/fzf-native-module.so | Bin 55384 -> 55384 bytes bin/Linux/fzf-native-module.so | Bin 61680 -> 61680 bytes bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 31744 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 8d443aa399c5309349c41ae7a0a71be64a223379..25f2233cb8c3c4cb0e4d9ed50efbf9dd658d264d 100755 GIT binary patch delta 4016 zcmaJ^3sh4_8lJf~DCHgUBtTwZLXekATOMsmeCtwCN_}Ak6cH>ItfE%*1PjNirO0*S zT16|UTTQH*H9mT3chxPhXt!#2Ew!z!iKs_cTium*6RKtZn|lQm$~`A%^38nzWB&iY z^Uu9(r!U*-`g)whFcXBCZzAs#LUCgzb2~#gXBh{&e7JEANyQnG{8bzZ;ReKF0l_}u zWIt9*%K6p%{3?qncGcm=GpUdbw|qWrYELS)FV~soh9>G9`GQCvs$59S&$OiaD3C>r zs*)+{``Wrn?5m3SFLZCWnv8{n@2)Tnx92LU(7(2-~q3L)xk%%0) zft(fP;z}}1TqvKaW+*{b_M^%%*3PWa)?a3rtnR$#{cWU4+>O&nlOzw1Cs>*>EfuV> z`ycKdCAOy}hRKkajZ+k?(+FLh51LsoT(rnGkcCXbNModJ!x)4!+MPp@XNz{6RRe;W zWS!%JbR5PviA3H&`Af+W`6oD?EDoDP73)Z_B8*%QGjM;il{GrBBC>gvd`DR;ltxc+ zg3;quv{&aaDTg6<0R06cbe+bIy5e2_`)XI=& zRj+TvKjJHm9^`iUq5qU3l*0yPBItHP3fQ6%LD8%sI8u)@i6L^7G+u(lomqdiKB~ml zM`IB40DSP5kT)ae;fv&}$e6Hd!IxbN10n1|5dL6DLkMt%z$H?p7QahUm8$2eDJ0H; zTw5r_kafr{Q;)Mg3&A?}q0p<)1(9cCz?Mw!CP?!^+9j3nKEXQb|>b0LjQm zm5N>@W{6rV9_o$NdNCPwQOiKmKXxxm^D&zLzS$x%X_pr(~+k~+kJ|os>8h0Y3k&-5JfL_ zR3EBQz)`EXbrZa?^T?moM(zE`UV-6#1j-{gflQ@Ww?QK#A88_WyHsAf3zF}xl_*Z6 zqr3b5{HkX;kq|Yx2cB=yh=^Qk@PU`Oql1jsCYruwOIZX}BjbdjBG|ong{MBp3CC%? z9M-|n@Vfz}?==(-TK16R+UIp5_*ad1O=6oCSdCOh`!YY{7-Xy!D9JWq&4Ysu0m4Xz-^qE#kV=|o`qnlQXv$WzhRKxTi5 z&LgpnJ!=r%drUY3@%HnKqJG5)^IrCZ?B>0|N45peuYey8Pkv)^xH3Z}S z>Rh)04giDD=rLfUCj|J$G+Ijn5u~(>2XDh+L&Im za4s+FHS0JT_Zz0_)#PM?0^cX?3B%#;L?!-#N{S&_iLuno7*d(2V27=Vh2r|rr7?`j zA2D}B;C_ZsJ(?Ng2u`|5DLVUDfM&y6sAq3C!J}IJsCfa7uV1`IQz0V9Nz=$kyv^~` z+(3*kIv(a#V7!f-pFiEvR^UUy!uk0pV1Svqj_XD7SdnP}=cP6eg)Aa(X|Oj{WIr~= zK!z8Oq_PYoulNmWtbtr8j(tX$z})WczG!d}sw9z}997aq2@^=-qHLwhmk9u$Yhld8 z0e9{~GpwEV5)%k>2iJy@U5k{g_&pH6=OV3(DnZ;a=JlI_@>NCgZxk#mS-v1my|}Ef zqI7|}w5*_TK_St<^&<&dl?_*)f>l{__Noxd*CV=FkFY!jq38reMI<7hxI{Fhc?3cz z6=8K6LjOodK3CEaPI&>L9n+Cue=b5bg@}4#0oU(wYM12@)Cq)@Dbn; zR@wxPAyuxizKv%w5}=wB$ti$H+v8`UaR|Ftcuw#*;&~2V;ITM`!$0sin#Xr}{3*nUeY!b3 z3b{?(gmeh!fP4&x<9OUWmcuXdSTNBM+x7!CHx_d9qrvH5_|-t5*=H7yS1;!9>pZ?v z&S5i;L-;SZIvyvD9ec9$!Fx@Nx@eenl>I%VY=mc-#?8z)Lul4%9K=^d@A@HPLN>FfLteGt&tFzL63?efi?6(y5ib!Yxj@%PEw%eLCy z|2+4^J3HO>)StIC1a;L;9Y0rXDIBtX>uV(+{%yv4l|LVxUTSOJ`BlN4x6hplE_?Om y#JT{&d|jo!{m`0T5Z@a4dhN;wD^D!3MV30#HZ0rw#e?J<**j-{UrI(?*8d;o`Zfdr delta 3936 zcmaJ^3se(V8oqZX2ofGal0X6^nE*+6<9iX61bkGhg06O}xJ3jN6-C8Y6`NQei=7-&V)jmE3aBY_PR`7C@Av)x zfB*l!a@|2+chI|b;RKc)9}&9(`5pHy=b7YcrJd*G)#9Za=80w8gaghwqPPIcApvv$#w?El zS*6G_Ef!h+9Yuc6=xDo(jXbip&M?zb$9N4fF90n)CejOJ;Og<&jXW>SVkwQly8d)_IA|c4_ z)y}l=Y|LauA?qxJ+Gf!-+b%YGT#itq@)@|>w@ z#2Yt$weKynzpPM1At{%qDAG?Obg6_`=Avk#Bkle?7=e`=|D>&J zJ+^h_A@(te!7GIoadYumxDsbjOQxDs0z4RVzhn;}c39=3); z(&?nuRtmAO6?H^u#@dFGWF5OF@fmC)ndfrAO_}aN1i=oKnwlK}pQhGM+K7?rn4Jt2P>ljn{~cAQL8w4|!hT*Yv^1e)tWq@Xhva z2gQB_u2%%T_|&TbA+0Ydy_3*w-{gm{iSI*Di+BgHCthvpHq#`u_Ria!KCCc;TX+($ zHg{XWeu8oIWyMc&F7(0Q=!gG<*8I!asr4gh@54cpAHFpHm+cpGX*0hxe-io-nEd2< zNFV%-eloiDCBB&~sO}P+_A`Qpmjo~H{RAZK+4+B7bUpT84am$G`ViEQPcf`=BFU-A9HVs62 zCXmIR#L%pPEcUuhg&j#8jxc(Xu+fu)gIY00)4SFekdr5$oIq2^Ni^BkxnPpbn+LlM z$Egd6ke?){LK5Lok^(GArt}aU%MRf0C&FRlU{ZD6GsQ)=*fso#=^&>$kuXd$!P24v3wBqnl}}V1TpLG$;;lajJ?! zF>Z6F&RK==7IKWQ`6W7UN0#m3|g4MHI^*wq^71p(V}T8CWs9oG1riH5e{i} zle4Xc9mR%{wj-w_;Nl_`Cx1lbj~IBmXg!fTD;M7jh03>Ep>*9O91DM0mp^-@8j)cm z+NwwRz8<0WG(@@6QNT~>sGoHpnKi#cxGf8zec34Ba5ln0BM~Zp1Buoal6z1oqC(~& zw0S-f9bJI1djXt5xQItkIw1>FWlh3VTf&v zQMy_%xxg<&Ub?cpcrMx_5+TcbTz550ZPW+;bc_4m4Ccm|Aa^tOeWwWyG!};)`WFWd zZSysqgotzLxEhS-KBn5F@bKJrbGZTep84dzMHq2m$mH<`0oMrl0|C1P{JDVL!+8Eh z0pA(U<68o57qDBvH6!?bFOLVK^CrGQ-j5I9elCww1#BI|<52>>Jl>gn{uVYbE9KjB zNzmcsH=QJ!du9rF(h?po7VzO^JT?p1wVcO01dPV=_<(?`C-S&nz*QAI{))%(NVJr1 zxGoUN1%r;4ce&UOE_N|O-bnVARm!Ry`E7sKJUb1#5 z$9K7zMYk{RZPh%!^>m-Y+&*IXttoj&P?9tGg!7tqIXk0s-_Sq*N!mVQ@?GSkF{6TbZC{r2FS<95D7*A!@eH@EKe zj`~$dk}qyH$;W(?^ZvZ|>RTqMg7@An)Kxu1z+SUxicM*k2cG)$eEgA=4=1nPCk|MM XtxFypT)2h)`on88TW-U^>-zr#=Rg%u diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index 99acb083dab615f2337e8d055dc88c76ec7b145c..ab30c08d3c3fb185c56c70d2e2c1586ca4469c0c 100755 GIT binary patch delta 4364 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 delta 8939 zcmeHM`(IVnwx4UlmiJQ@Z*e0VLRf zZ=X7$Hy?HFUJ5I;7w1TVS5hjkS$R)X-omirl%;+?<+|Uo)Jf+^tjNb^GV)o$PiL3;QL|u7A%#mWHZc#1Wky_8#InG$m zX2y0>X**+s5^xt|TjClCc(1RT1W&WWA@+3T@wF zJbXbVBA!RNp`3++%EWR$*{um2s%7_!MCk}oTwf| z&rxs|_N`^~WiN6#cqlKd)@>ZU`%C^J@J~ zRkORyh>~wVtI9b=BGqkmf@U^EHWr1{VNk%>`Ia#vX!geo#oKP_0ecjR`++LIT|f*x zItzq{`ycPn0-aS_k6K!^hE8%?&zL%(w0V6-Mf$KTRFt+^KVg+9tyW7c>hTuuxkpNc z;!F4Va6N+Na^7%3Ul%Gcs})l{Vl=ZrQR5LH-!g4|+|o|fwaLdQNPyVq5fXi?w9V^~ z76^6arF1K;#-x=XVubj@BSE+ zy0li8>U2rbr3_t~MpBsTeOeHIz32yBx|Xk1_wTxNNtc>*>69)l)1}vRDPEWUtV`+! zEoP%Goztb&y7ZDR<>=BGU5e7Bf72yrT}soX8;aHzFJ1aUmp;OR!DxN0OE2hBpDtN- z=^aJP@C};=A>|{*zN3l87i(|;o8&hrX~WdhQmfDIqOFW=^2>Vhu&-HKFP_z&Z~2-d z?Zy(dzirV1w3^OhMPhwC+Cv$k;zVbL-x?{ z+k-zJQB}p>OhNLo&+MnQhD=%~9-dgrle5K{iE$pU)G;>vH5ziHPVj)(fTfj)n$mM? zb=YUs*NJ%nv!xc%5HN$E%oc|OQh8&o_L zw6gv4h96T-sd63U?P>$^V4j(h#Q@Sv6ohh2?}-fZUyZy5eUaTXTtzZw@B{T@#ePz; z@6@-A_%tNTKT0#cL5;n;1kNcLA|bRqFb+QIz~kKwpJ5Z$(ZiWB|XkA^v}RB66~k^EG#yQ%V%nrWd~m77ML9-1z8L|^8K)5WHkNBEJX z&YxobCh@F9@kVSV|9gT6j$6UECx{2*Hgn$u@lD(!-W}gLJARVH^W!_0CA=t=I>j=U zn`X7-T0LlX%T@UwQ%Q5wQZ6y3E7z&=MNMh7N=ApuZqslA8MD*NvemO!c znHv}OZUkdPD}%9c;50<(AmNOn{#BK$LdBlBNihv5N1hz=A`|-s#r}zs@I}7;Op*PB zDj&u52wQ7egt$C6l|K|JCZ|V)T$@CF!FG>9=`py$@470thlu6rfATpVg3`9d5@M+c+V@~&jpIR=Ed-xf#R`w`F=BCiuPAyfGFoZ9Vjl&Q#>xn z#=xDG#q{}i@y)WZ&yV9-vUp>D1UJj#{Cq19l|@n3N`5Us9Ljo>Uz#Z5vpe~YiQ1{Sm?Zq~oSNeVAEkNlX0Pe@ zX^hb*_L{DN)7Dt4=@M9<8F~zy8&1t#0l!_nPj3NLS=m(^_!HV1=A)Dg>V!bqe8sS6WS((}c88o)ZIQ zvsVJ1St$6t*AxPlF6c0E2RrNnPYI0A@o$1Qdn!K1M{wwweoPdL7skdlpg?)_R2=%F zT>DUo@;aJGo?fp2GNdgYO7oZRLHtXiXkQq@A4?Q(Ej;8CHxuD~w!e=js+%buR3cL! z^MYTS*Ji-bdUt&z4lP3;9`ClAG2=`1@�$i}GGm&kS)=$?`bkiJq$;s;2)<5Ptdd z_<;nmBtIx&X95gMof&)b0v5WtIn#dzM!I44P#t0paTE-F@2br}vJm+zxod*>AwST+ z#0Sm5ayGSY8sz53L`{#D2ug#o|_GK~R5M zhOL^e!54BU$q(x`Ni(~LQ)+!4Fum@EN}K!CpM7kmFZKDOX(Z8@&YvNkG`$lqzFZvR zKkG$RZD>^1daVP4tr7jA2r1be@Ey&Yc8AH*ZZ1ukU&M)bOQPLAh4YDC(>HPAN=Xc_ zb`k+g5_qwb$X*i6hqWj*3y1k#KV|D{QMhrOf)ddz*PorHSk{y+;3os;Xl{Fn-A%6 zoDKO0!|sBC?;5CEF&hiU#|`ag;IH^}LBA^oer4bV1NBnIRvGqX1{N4tYv8C|jnSYY z1Fst8$(n3q>?=cY&cHqcy$y%aK+hQZod&*aV55OY4Sd(Y#|&&Y@c!H4)sueyXmnuX z8rFpAx`n=X>vHmKIO?(s_V^H$`&#*RE&?iI-MgD~hssAqLB#`3kWd`p*;1ve_X44i z#Xl@cv)K&o`~a~o0}E5{AZWUuys~Hm@hRe@D7H|o^%b}L*YZ#;CJo|3O zo(5_^x*#oPO#Av~*?7OEL%D4>xKX7gIr)~8uEVmc#I+COn{;nYm9@$;iySdDERFn< zlh@Xw;oXp_!lVUD%Pi#!vdN}zyzVa24UE&}lbwxS7_Za}2gm73$dC<<*J*Ym<8)=3 z-SzQ0&5l(g0Mc~QCh7!ungT%5Ch^1?ADV+zm>9j(vUTg$nrf(6-@Qh+Hd)tN^0T$* z1LJgeY0)n@>dJJx!Erj>ZisX)sHahH#YnyWan+V|kFKDvthCXMv7QN*W#C(Bz?d$D zbS2O^%n7X@MjQQE91?~vO{;d>YzHJ6y>4&9Fyib=s@{y6@Y|(*#hu2I>PIcK`Nl|W z-n7<|jO!2NcEpk3r0Zs4;l_%gyC;R9H<5wPEU?B8@14FZIS?OjK|}Y7h{`~zM9iv8 zw5^AYi8E8GD+@+1(q&|S(jo5c^58^Wmx1z-a1-;A0@#ER=RsbA4uddQEYg)T*Hjq# zGFAZEOR-2-9gi?y5HV;?m5ySK9xJl<9tv~_1ua}*LO=O zMq{!FtiH=uf<-YKi=YTgn(AaIu%>B@(d)rZvX5|O5$8Nv zpw)|c)p#>gnU7ZHix-c*Mm(~0lpX!0Ko{|2b;z9^g^c|UxBzHGc6Lj(PI9h&LjD^1{R!csn>b^DSt zqpv(Epz9Pjz?B79P0Xjpow=8}X$zFf@{(B;{&mpXYbb8XV*$j{nkhL0*^IU2V9}Gg zyEAjg5-?$DknUyBeMXqu@mw>@(iV^4u7 zsnhQ#@V`6AT5o6a5QGMdV|uwr zsg-4lBO%T)=$=`Q9R@030eRu2dxJ+g6Kb;XF;8t zJI~ZzlWbO)8^5$IXn!Sgdc&v&E)U_I0lGd1-CpP#t3VFA?;+Q7<4PVz> zVM7}!9u$m3O&iJ%GJ_vGQ^p@cjt7*(;bqfKu?FnK4)lO<2dD=;VLOQac_KUtx&)r^ z9ne+qgtUw4|1ZL~qOohC?*_gVgTIi82kyX*E&<;T439GxgZ4{L4ycALH*zfDLq{TKbHP+j@Y2YVWFK3 zgeO4(Nce>3K&jvfFM)EwQ+^Ab0U12yxy1%Qn(q=(f#bPN=lT3BbO3t76`*b4DKDNx zUbOWf(AY_$pG2dl4&fh>IGbQYxC68oJmJfr!{8~g<|47uI-msWhLiIO^o}Peox=SE z@WsdxnhKFS!2@koIz*9zk3DiXu07q{Gjwe6e2rR`IE!Y!gf?~lF-U&(r zPZ|A}ML_WM*76LXj>|9$AhC)$4c}JT1U0K(lRyK6GZ_&!BZMx=L7%27o6F z!h6~_@WH@z;^Djw*lh5R1K%8j6qCq?%T^@)hr0YT@>^;zNpzA=^!SaKZ2iF~}KiGJ%)35Bj F^nYzKiUa@v From a77b52e689fc3bf755e8190748f379e91c4566f8 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 20:14:23 -0400 Subject: [PATCH 28/47] Add BSD --- .github/workflows/cmake-binaries.yaml | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-binaries.yaml b/.github/workflows/cmake-binaries.yaml index 32a105b..f9155cc 100644 --- a/.github/workflows/cmake-binaries.yaml +++ b/.github/workflows/cmake-binaries.yaml @@ -42,8 +42,35 @@ jobs: if-no-files-found: error retention-days: 1 + build-freebsd: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Clean stale binaries + run: rm -rf bin + + - name: Build in FreeBSD VM + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + prepare: | + pkg install -y cmake + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + cmake --build build --verbose --config ${{env.BUILD_TYPE}} + + - name: Upload binaries + uses: actions/upload-artifact@v4 + with: + name: bin-freebsd + path: bin/ + if-no-files-found: error + retention-days: 1 + commit: - needs: build + needs: [build, build-freebsd] runs-on: ubuntu-latest permissions: contents: write From ffd36b7eac666e8b00a382013b60148f8ba988aa Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 00:17:07 +0000 Subject: [PATCH 29/47] Update binaries for all platforms --- bin/FreeBSD/fzf-native-module.so | Bin 117344 -> 52088 bytes bin/Windows/Release/fzf-native-module.dll | Bin 31744 -> 31744 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/FreeBSD/fzf-native-module.so b/bin/FreeBSD/fzf-native-module.so index 4a37ae027273654969f12734c013b0cd6927804c..c7e92668e063076fd548b514b988280ba491f7e2 100755 GIT binary patch literal 52088 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 Y*%h`P&)|O;-61^tVBeFq`VN!n3Wd{=&;*O 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 S!@eQ>-+oppKOZUFnE&4}ESix3 diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index b380845280c516f2a3a9dd33c15ec0d62734add2..d00ee894a03b1997059d9f08c337f0d24a23402b 100644 GIT binary patch delta 25 ecmZqp!PxMFaf1XSllr;MQjA^EAbMF$lso`{z6px} delta 25 ecmZqp!PxMFaf1XSlla-qQjA^EAbMF$lso``N(pHI From 3722ec9dd210057cf0478efbff94dac837932943 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 20:18:43 -0400 Subject: [PATCH 30/47] Revert "Add test message" This reverts commit cba91ff8988fe199056a8bb74b027c9108b79475. --- fzf-native-module.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 9bbf664..d06d908 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -1593,10 +1593,5 @@ int emacs_module_init(struct emacs_runtime *rt) { Qzero = env->make_global_ref(env, env->make_integer(env, 0)); Qone = env->make_global_ref(env, env->make_integer(env, 1)); - /* Unconditional load marker — visible in *Messages* after rebuild + reload. */ - emacs_value load_msg = env->make_string(env, "fzf-native: module loaded", - (ptrdiff_t)sizeof("fzf-native: module loaded") - 1); - env->funcall(env, Fmessage, 1, &load_msg); - return 0; } From 6080d49777ef6dcda3e2c0065f0139810fe28888 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 00:21:40 +0000 Subject: [PATCH 31/47] Update binaries for all platforms --- bin/Darwin/arm64/fzf-native-module.so | Bin 55384 -> 55384 bytes bin/FreeBSD/fzf-native-module.so | Bin 52088 -> 52008 bytes bin/Linux/fzf-native-module.so | Bin 61680 -> 61680 bytes bin/Windows/Release/fzf-native-module.dll | Bin 31744 -> 31232 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 25f2233cb8c3c4cb0e4d9ed50efbf9dd658d264d..8d443aa399c5309349c41ae7a0a71be64a223379 100755 GIT binary patch delta 3936 zcmaJ^3se(V8oqZX2ofGal0X6^nE*+6<9iX61bkGhg06O}xJ3jN6-C8Y6`NQei=7-&V)jmE3aBY_PR`7C@Av)x zfB*l!a@|2+chI|b;RKc)9}&9(`5pHy=b7YcrJd*G)#9Za=80w8gaghwqPPIcApvv$#w?El zS*6G_Ef!h+9Yuc6=xDo(jXbip&M?zb$9N4fF90n)CejOJ;Og<&jXW>SVkwQly8d)_IA|c4_ z)y}l=Y|LauA?qxJ+Gf!-+b%YGT#itq@)@|>w@ z#2Yt$weKynzpPM1At{%qDAG?Obg6_`=Avk#Bkle?7=e`=|D>&J zJ+^h_A@(te!7GIoadYumxDsbjOQxDs0z4RVzhn;}c39=3); z(&?nuRtmAO6?H^u#@dFGWF5OF@fmC)ndfrAO_}aN1i=oKnwlK}pQhGM+K7?rn4Jt2P>ljn{~cAQL8w4|!hT*Yv^1e)tWq@Xhva z2gQB_u2%%T_|&TbA+0Ydy_3*w-{gm{iSI*Di+BgHCthvpHq#`u_Ria!KCCc;TX+($ zHg{XWeu8oIWyMc&F7(0Q=!gG<*8I!asr4gh@54cpAHFpHm+cpGX*0hxe-io-nEd2< zNFV%-eloiDCBB&~sO}P+_A`Qpmjo~H{RAZK+4+B7bUpT84am$G`ViEQPcf`=BFU-A9HVs62 zCXmIR#L%pPEcUuhg&j#8jxc(Xu+fu)gIY00)4SFekdr5$oIq2^Ni^BkxnPpbn+LlM z$Egd6ke?){LK5Lok^(GArt}aU%MRf0C&FRlU{ZD6GsQ)=*fso#=^&>$kuXd$!P24v3wBqnl}}V1TpLG$;;lajJ?! zF>Z6F&RK==7IKWQ`6W7UN0#m3|g4MHI^*wq^71p(V}T8CWs9oG1riH5e{i} zle4Xc9mR%{wj-w_;Nl_`Cx1lbj~IBmXg!fTD;M7jh03>Ep>*9O91DM0mp^-@8j)cm z+NwwRz8<0WG(@@6QNT~>sGoHpnKi#cxGf8zec34Ba5ln0BM~Zp1Buoal6z1oqC(~& zw0S-f9bJI1djXt5xQItkIw1>FWlh3VTf&v zQMy_%xxg<&Ub?cpcrMx_5+TcbTz550ZPW+;bc_4m4Ccm|Aa^tOeWwWyG!};)`WFWd zZSysqgotzLxEhS-KBn5F@bKJrbGZTep84dzMHq2m$mH<`0oMrl0|C1P{JDVL!+8Eh z0pA(U<68o57qDBvH6!?bFOLVK^CrGQ-j5I9elCww1#BI|<52>>Jl>gn{uVYbE9KjB zNzmcsH=QJ!du9rF(h?po7VzO^JT?p1wVcO01dPV=_<(?`C-S&nz*QAI{))%(NVJr1 zxGoUN1%r;4ce&UOE_N|O-bnVARm!Ry`E7sKJUb1#5 z$9K7zMYk{RZPh%!^>m-Y+&*IXttoj&P?9tGg!7tqIXk0s-_Sq*N!mVQ@?GSkF{6TbZC{r2FS<95D7*A!@eH@EKe zj`~$dk}qyH$;W(?^ZvZ|>RTqMg7@An)Kxu1z+SUxicM*k2cG)$eEgA=4=1nPCk|MM XtxFypT)2h)`on88TW-U^>-zr#=Rg%u delta 4016 zcmaJ^3sh4_8lJf~DCHgUBtTwZLXekATOMsmeCtwCN_}Ak6cH>ItfE%*1PjNirO0*S zT16|UTTQH*H9mT3chxPhXt!#2Ew!z!iKs_cTium*6RKtZn|lQm$~`A%^38nzWB&iY z^Uu9(r!U*-`g)whFcXBCZzAs#LUCgzb2~#gXBh{&e7JEANyQnG{8bzZ;ReKF0l_}u zWIt9*%K6p%{3?qncGcm=GpUdbw|qWrYELS)FV~soh9>G9`GQCvs$59S&$OiaD3C>r zs*)+{``Wrn?5m3SFLZCWnv8{n@2)Tnx92LU(7(2-~q3L)xk%%0) zft(fP;z}}1TqvKaW+*{b_M^%%*3PWa)?a3rtnR$#{cWU4+>O&nlOzw1Cs>*>EfuV> z`ycKdCAOy}hRKkajZ+k?(+FLh51LsoT(rnGkcCXbNModJ!x)4!+MPp@XNz{6RRe;W zWS!%JbR5PviA3H&`Af+W`6oD?EDoDP73)Z_B8*%QGjM;il{GrBBC>gvd`DR;ltxc+ zg3;quv{&aaDTg6<0R06cbe+bIy5e2_`)XI=& zRj+TvKjJHm9^`iUq5qU3l*0yPBItHP3fQ6%LD8%sI8u)@i6L^7G+u(lomqdiKB~ml zM`IB40DSP5kT)ae;fv&}$e6Hd!IxbN10n1|5dL6DLkMt%z$H?p7QahUm8$2eDJ0H; zTw5r_kafr{Q;)Mg3&A?}q0p<)1(9cCz?Mw!CP?!^+9j3nKEXQb|>b0LjQm zm5N>@W{6rV9_o$NdNCPwQOiKmKXxxm^D&zLzS$x%X_pr(~+k~+kJ|os>8h0Y3k&-5JfL_ zR3EBQz)`EXbrZa?^T?moM(zE`UV-6#1j-{gflQ@Ww?QK#A88_WyHsAf3zF}xl_*Z6 zqr3b5{HkX;kq|Yx2cB=yh=^Qk@PU`Oql1jsCYruwOIZX}BjbdjBG|ong{MBp3CC%? z9M-|n@Vfz}?==(-TK16R+UIp5_*ad1O=6oCSdCOh`!YY{7-Xy!D9JWq&4Ysu0m4Xz-^qE#kV=|o`qnlQXv$WzhRKxTi5 z&LgpnJ!=r%drUY3@%HnKqJG5)^IrCZ?B>0|N45peuYey8Pkv)^xH3Z}S z>Rh)04giDD=rLfUCj|J$G+Ijn5u~(>2XDh+L&Im za4s+FHS0JT_Zz0_)#PM?0^cX?3B%#;L?!-#N{S&_iLuno7*d(2V27=Vh2r|rr7?`j zA2D}B;C_ZsJ(?Ng2u`|5DLVUDfM&y6sAq3C!J}IJsCfa7uV1`IQz0V9Nz=$kyv^~` z+(3*kIv(a#V7!f-pFiEvR^UUy!uk0pV1Svqj_XD7SdnP}=cP6eg)Aa(X|Oj{WIr~= zK!z8Oq_PYoulNmWtbtr8j(tX$z})WczG!d}sw9z}997aq2@^=-qHLwhmk9u$Yhld8 z0e9{~GpwEV5)%k>2iJy@U5k{g_&pH6=OV3(DnZ;a=JlI_@>NCgZxk#mS-v1my|}Ef zqI7|}w5*_TK_St<^&<&dl?_*)f>l{__Noxd*CV=FkFY!jq38reMI<7hxI{Fhc?3cz z6=8K6LjOodK3CEaPI&>L9n+Cue=b5bg@}4#0oU(wYM12@)Cq)@Dbn; zR@wxPAyuxizKv%w5}=wB$ti$H+v8`UaR|Ftcuw#*;&~2V;ITM`!$0sin#Xr}{3*nUeY!b3 z3b{?(gmeh!fP4&x<9OUWmcuXdSTNBM+x7!CHx_d9qrvH5_|-t5*=H7yS1;!9>pZ?v z&S5i;L-;SZIvyvD9ec9$!Fx@Nx@eenl>I%VY=mc-#?8z)Lul4%9K=^d@A@HPLN>FfLteGt&tFzL63?efi?6(y5ib!Yxj@%PEw%eLCy z|2+4^J3HO>)StIC1a;L;9Y0rXDIBtX>uV(+{%yv4l|LVxUTSOJ`BlN4x6hplE_?Om y#JT{&d|jo!{m`0T5Z@a4dhN;wD^D!3MV30#HZ0rw#e?J<**j-{UrI(?*8d;o`Zfdr diff --git a/bin/FreeBSD/fzf-native-module.so b/bin/FreeBSD/fzf-native-module.so index c7e92668e063076fd548b514b988280ba491f7e2..90c190ffd22a4d6307be3bf40331a2a08bbfdf61 100755 GIT binary patch delta 8110 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 Z!>|iQ+Y$KmVlIgU{iQte2$WnJ_y6LUXD|Q& delta 8188 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 From 5095552f0c22eb2d60d4742a4daba8772001e9b5 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 20:51:59 -0400 Subject: [PATCH 32/47] Add phase 1 cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-session LRU result cache that maps query strings to scored snapshots, sitting on top of the existing batch-parallel scoring path. Reintroduces just the cache half of the reverted 76b7318 — none of the range-based-worker / per_worker_limit machinery that caused the under-counting bugs. Cache anatomy: - SharedIdx: refcounted flexible-array struct of uint32_t holding the full set of matched candidate indices for a scoring run. Allocated by the scoring thread on publish, retained in O(1) by lookup consumers under the cache mutex (no memcpy), freed by the last consumer. - CacheEntry { query, pool_gen, top[K], m_idx } in a doubly-linked LRU list, mutex-protected, MRU at head. Successful lookups bump the entry to MRU. Inserts pre-allocate everything outside the mutex; the critical section is just pointer swaps + LRU manipulation. - subsumes(Q', Q): byte-prefix match + reject any '|'. OR queries can never serve as refinement sources because adding an OR alternate widens the result set unpredictably. - cache_lookup_exact: exact query match; returns cached top-K + bumped SharedIdx + pool_gen. cache_lookup_prefix: longest subsuming Q' with non-NULL m_idx (entries from OR queries are skipped). - cache_insert: drops m_idx for OR queries (still inserts the entry so it can serve future exact lookups, but it can't be a refinement source). Scoring-thread integration: - ScoredStr gained a uint32_t idx field. The batch-construction loop populates it from a parallel snap_idx[] array; the worker preserves it via existing struct copy; counting_sort_scored preserves it the same way. - Refinement mode: when score_req_refine_idx is non-NULL, the snap is the union of those indices and s->cands[refine_delta_from..count] instead of the full pool. For typing past the first 2-3 chars this is typically <1% of pool size. - On publish, builds the full match-set index array (all pos matches, not just the top-K emitted) and calls cache_insert with it, so a later subsuming query can refine over the complete prior match set. Dispatch (fzf_native_async_candidates): Lookup result | Display | Scoring scheduled ---------------------+----------------------+---------------------------- Exact, pool_gen==now | Cached top-K | None (no work) Exact, pool_genidx); + if (!p) return NULL; + atomic_init(&p->refcount, 1); + p->count = n; + memcpy(p->idx, src, n * sizeof *p->idx); + return p; +} +static SharedIdx *shared_idx_retain(SharedIdx *p) { + if (p) atomic_fetch_add_explicit(&p->refcount, 1, memory_order_relaxed); + return p; +} +static void shared_idx_release(SharedIdx *p) { + if (p && atomic_fetch_sub_explicit(&p->refcount, 1, memory_order_acq_rel) == 1) + free(p); +} + +/* LRU result cache. Per-session, mutex-protected doubly-linked list with + MRU at head and LRU at tail. Each entry records: + query — the literal filter string (owned, strdup'd) + pool_gen — s->count at the moment the entry was scored + top — copy of the top-K ScoredStr published to Elisp + m_idx — SharedIdx of all matched candidate indices (NULL for OR + queries, which can never serve as refinement sources because + adding an OR alternate widens the result set) + + Lookups happen on the Emacs main thread (dispatch); inserts happen on + the scoring thread (publish). Both serialize through cache->mu. */ +typedef struct CacheEntry { + struct CacheEntry *prev, *next; + char *query; + size_t pool_gen; + ScoredStr *top; + size_t top_count; + SharedIdx *m_idx; +} CacheEntry; + +typedef struct { + pthread_mutex_t mu; + CacheEntry *head; /* MRU */ + CacheEntry *tail; /* LRU */ + size_t count; + size_t max_entries; +} Cache; + +static void cache_init(Cache *c, size_t max_entries) { + pthread_mutex_init(&c->mu, NULL); + c->head = c->tail = NULL; + c->count = 0; + c->max_entries = max_entries ? max_entries : 20; +} + +static void cache_entry_free(CacheEntry *e) { + if (!e) return; + free(e->query); + free(e->top); + shared_idx_release(e->m_idx); + free(e); +} + +static void cache_unlink_locked(Cache *c, CacheEntry *e) { + if (e->prev) e->prev->next = e->next; else c->head = e->next; + if (e->next) e->next->prev = e->prev; else c->tail = e->prev; + e->prev = e->next = NULL; + c->count--; +} + +static void cache_push_head_locked(Cache *c, CacheEntry *e) { + e->prev = NULL; + e->next = c->head; + if (c->head) c->head->prev = e; + c->head = e; + if (!c->tail) c->tail = e; + c->count++; +} + +static void cache_free(Cache *c) { + pthread_mutex_lock(&c->mu); + CacheEntry *e = c->head; + while (e) { CacheEntry *n = e->next; cache_entry_free(e); e = n; } + c->head = c->tail = NULL; + c->count = 0; + pthread_mutex_unlock(&c->mu); + pthread_mutex_destroy(&c->mu); +} + +/* Q' subsumes Q iff neither contains '|' AND Q' is a byte-prefix of Q. + Captures: extending a term, adding AND terms, adding negations, adding + anchors. Conservatively rejects every query containing '|' — adding an + OR alternate widens results unpredictably. */ +static bool subsumes(const char *q_prime, const char *q) { + if (strchr(q_prime, '|') || strchr(q, '|')) return false; + size_t lp = strlen(q_prime); + if (lp == 0) return true; + size_t lq = strlen(q); + if (lq < lp) return false; + return memcmp(q, q_prime, lp) == 0; +} + +/* Find an entry by exact query match. Caller holds c->mu. */ +static CacheEntry *cache_find_locked(Cache *c, const char *query) { + for (CacheEntry *e = c->head; e; e = e->next) + if (strcmp(e->query, query) == 0) return e; + return NULL; +} + +/* Exact lookup. On hit, bumps entry to MRU and returns: + *out_top, *out_top_count — caller-owned copy of the cached top-K + *out_m_idx — SharedIdx with refcount bumped (caller releases) + *out_pool_gen — pool size at the time this entry was scored + Returns true on hit, false on miss. */ +static bool cache_lookup_exact(Cache *c, const char *query, + ScoredStr **out_top, size_t *out_top_count, + SharedIdx **out_m_idx, size_t *out_pool_gen) { + pthread_mutex_lock(&c->mu); + CacheEntry *e = cache_find_locked(c, query); + if (!e) { pthread_mutex_unlock(&c->mu); return false; } + + ScoredStr *top_copy = NULL; + if (e->top_count) { + top_copy = malloc(e->top_count * sizeof *top_copy); + if (top_copy) memcpy(top_copy, e->top, e->top_count * sizeof *top_copy); + } + *out_top = top_copy; + *out_top_count = top_copy ? e->top_count : 0; + *out_m_idx = shared_idx_retain(e->m_idx); + *out_pool_gen = e->pool_gen; + + /* Bump to MRU. */ + if (e != c->head) { cache_unlink_locked(c, e); cache_push_head_locked(c, e); } + pthread_mutex_unlock(&c->mu); + return true; +} + +/* Prefix lookup: longest Q' that subsumes Q (and is not Q itself). Skips + entries with NULL m_idx — they can't serve as refinement sources. */ +static bool cache_lookup_prefix(Cache *c, const char *query, + ScoredStr **out_top, size_t *out_top_count, + SharedIdx **out_m_idx, size_t *out_pool_gen) { + if (strchr(query, '|')) return false; /* fast reject */ + + pthread_mutex_lock(&c->mu); + CacheEntry *best = NULL; + size_t best_len = 0; + for (CacheEntry *e = c->head; e; e = e->next) { + if (!e->m_idx) continue; + if (strcmp(e->query, query) == 0) continue; + if (!subsumes(e->query, query)) continue; + size_t l = strlen(e->query); + if (l > best_len) { best = e; best_len = l; } + } + if (!best) { pthread_mutex_unlock(&c->mu); return false; } + + ScoredStr *top_copy = NULL; + if (best->top_count) { + top_copy = malloc(best->top_count * sizeof *top_copy); + if (top_copy) memcpy(top_copy, best->top, best->top_count * sizeof *top_copy); + } + *out_top = top_copy; + *out_top_count = top_copy ? best->top_count : 0; + *out_m_idx = shared_idx_retain(best->m_idx); + *out_pool_gen = best->pool_gen; + + if (best != c->head) { cache_unlink_locked(c, best); cache_push_head_locked(c, best); } + pthread_mutex_unlock(&c->mu); + return true; +} + +/* Insert or update an entry. Performs all allocations BEFORE taking + c->mu, so the critical section is just pointer swaps + LRU manipulation. + Evicted entries are freed after the unlock. m_idx may be NULL (OR queries + or empty match sets); the entry is still inserted, but is then ineligible + as a prefix-refinement source. */ +static void cache_insert(Cache *c, const char *query, size_t pool_gen, + const ScoredStr *top, size_t top_count, + const uint32_t *m_idx_src, size_t m_idx_count) { + /* Pre-allocate everything outside the mutex. */ + char *q_dup = strdup(query); + ScoredStr *top_dup = NULL; + if (top_count && top) { + top_dup = malloc(top_count * sizeof *top_dup); + if (top_dup) memcpy(top_dup, top, top_count * sizeof *top_dup); + else top_count = 0; + } + SharedIdx *sidx = (m_idx_src && m_idx_count && !strchr(query, '|')) + ? shared_idx_alloc(m_idx_src, m_idx_count) : NULL; + + if (!q_dup) { + free(top_dup); + shared_idx_release(sidx); + return; + } + + pthread_mutex_lock(&c->mu); + CacheEntry *e = cache_find_locked(c, query); + if (e) { + /* Update existing entry: swap fields, release old refs after unlock. */ + char *old_q = e->query; + ScoredStr *old_top = e->top; + SharedIdx *old_idx = e->m_idx; + e->query = q_dup; + e->top = top_dup; + e->top_count = top_dup ? top_count : 0; + e->m_idx = sidx; + e->pool_gen = pool_gen; + if (e != c->head) { cache_unlink_locked(c, e); cache_push_head_locked(c, e); } + pthread_mutex_unlock(&c->mu); + free(old_q); + free(old_top); + shared_idx_release(old_idx); + return; + } + + /* New entry. */ + CacheEntry *ne = calloc(1, sizeof *ne); + if (!ne) { + pthread_mutex_unlock(&c->mu); + free(q_dup); + free(top_dup); + shared_idx_release(sidx); + return; + } + ne->query = q_dup; + ne->top = top_dup; + ne->top_count = top_dup ? top_count : 0; + ne->m_idx = sidx; + ne->pool_gen = pool_gen; + cache_push_head_locked(c, ne); + + /* Evict LRU if over capacity. */ + CacheEntry *evicted = NULL; + if (c->count > c->max_entries && c->tail) { + evicted = c->tail; + cache_unlink_locked(c, evicted); + } + pthread_mutex_unlock(&c->mu); + cache_entry_free(evicted); +} typedef struct { pthread_t reader; @@ -753,6 +1006,11 @@ typedef struct { pthread_cond_t score_req_cond; char *score_req_filter; /* owned; NULL = nothing pending */ size_t score_req_limit; + /* Refinement request: when score_req_refine_idx is non-NULL the next scoring + run scores only those candidate indices plus s->cands[refine_delta_from..count]. + Ownership transfers to the scoring thread along with score_req_filter. */ + SharedIdx *score_req_refine_idx; + size_t score_req_refine_delta_from; bool score_req_stop; _Atomic bool score_abort; /* set to cancel in-flight workers */ @@ -763,6 +1021,11 @@ typedef struct { ScoredStr *score_results; /* latest scored+sorted results */ size_t score_count; /* number of entries in score_results */ + /* Result cache (LRU keyed by query, values include matched_idx for + prefix refinement). Read on dispatch (main thread); written on + scoring publish (scoring thread). */ + Cache cache; + /* Read-only after session start; set from fzf-async-max-line-length defcustom. 0 = no limit. >0 = exclude lines longer than N chars. <0 = truncate to |N|. */ ptrdiff_t max_line_length; @@ -826,6 +1089,8 @@ static void async_session_destroy(void *ptr) { pthread_mutex_lock(&s->score_req_mu); free(s->score_req_filter); s->score_req_filter = NULL; + shared_idx_release(s->score_req_refine_idx); + s->score_req_refine_idx = NULL; s->score_req_stop = true; pthread_cond_signal(&s->score_req_cond); pthread_mutex_unlock(&s->score_req_mu); @@ -833,6 +1098,7 @@ static void async_session_destroy(void *ptr) { free(s->score_results); free(s->score_current_filter); + cache_free(&s->cache); pthread_mutex_destroy(&s->score_res_mu); pthread_mutex_destroy(&s->score_req_mu); pthread_cond_destroy(&s->score_req_cond); @@ -1030,6 +1296,22 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, } } + { + size_t cache_max = 20; + emacs_value sym = env->intern(env, "fzf-async-cache-size"); + emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (!env->eq(env, val, Qnil)) { + intmax_t n = env->extract_integer(env, val); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) + env->non_local_exit_clear(env); + else if (n > 0) + cache_max = (size_t)n; + } + cache_init(&s->cache, cache_max); + } + if (!s->fp || !s->cands || pthread_create(&s->reader, NULL, async_reader, s) != 0 || pthread_create(&s->score_thread, NULL, scoring_thread_fn, s) != 0) { @@ -1150,9 +1432,12 @@ static void *scoring_thread_fn(void *arg) { pthread_mutex_unlock(&s->score_req_mu); break; } - char *filter = s->score_req_filter; /* steal ownership */ - size_t limit = s->score_req_limit; - s->score_req_filter = NULL; + char *filter = s->score_req_filter; /* steal ownership */ + size_t limit = s->score_req_limit; + SharedIdx *refine_idx = s->score_req_refine_idx; /* steal */ + size_t refine_delta_from = s->score_req_refine_delta_from; + s->score_req_filter = NULL; + s->score_req_refine_idx = NULL; /* Record what we're about to score so main thread can skip abort for same filter */ free(s->score_current_filter); s->score_current_filter = strdup(filter); @@ -1170,24 +1455,54 @@ static void *scoring_thread_fn(void *arg) { size_t count = s->count; pthread_mutex_unlock(&s->mu); - char **snap = count ? malloc(count * sizeof *snap) : NULL; - if (!snap && count) { + /* Determine snap layout. In full mode, snap[i] = s->cands[i] for + i in [0, count); snap_count = count. In refine mode, snap is the + union of refine_idx (the previous match set) and the delta range + s->cands[refine_delta_from .. count) — always a strict subset of + the full pool, hence the speedup. */ + bool refine = (refine_idx != NULL && refine_delta_from <= count); + size_t delta_len = refine ? (count - refine_delta_from) : 0; + size_t snap_count = refine ? (refine_idx->count + delta_len) : count; + + char **snap = snap_count ? malloc(snap_count * sizeof *snap) : NULL; + uint32_t *snap_idx = snap_count ? malloc(snap_count * sizeof *snap_idx) : NULL; + if (snap_count && (!snap || !snap_idx)) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(filter); continue; + free(snap); free(snap_idx); free(filter); shared_idx_release(refine_idx); continue; } pthread_mutex_lock(&s->mu); if (s->count < count) count = s->count; /* cap if reader shrank (shouldn't happen) */ - if (snap) memcpy(snap, s->cands, count * sizeof *snap); + if (refine) { + /* Cap delta range too in case reader shrank. */ + if (refine_delta_from > count) refine_delta_from = count; + delta_len = count - refine_delta_from; + /* Refine entries first: validate each refine_idx[i] < count. */ + size_t w = 0; + for (size_t i = 0; i < refine_idx->count; i++) { + uint32_t gi = refine_idx->idx[i]; + if (gi < count) { snap[w] = s->cands[gi]; snap_idx[w] = gi; w++; } + } + /* Delta entries. */ + for (size_t k = 0; k < delta_len; k++) { + size_t gi = refine_delta_from + k; + snap[w] = s->cands[gi]; snap_idx[w] = (uint32_t)gi; w++; + } + snap_count = w; + } else if (snap) { + memcpy(snap, s->cands, count * sizeof *snap); + for (size_t i = 0; i < count; i++) snap_idx[i] = (uint32_t)i; + snap_count = count; + } pthread_mutex_unlock(&s->mu); /* Batch; check abort every 64 K items so a filter change is noticed quickly. */ struct AsyncScoringBatch *batches = NULL; size_t bi = 0, bcap = 0; bool batch_ok = true; - for (size_t i = 0; i < count; i++) { + for (size_t i = 0; i < snap_count; i++) { if ((i & 0xFFFF) == 0 && atomic_load_explicit(&s->score_abort, memory_order_relaxed)) { batch_ok = false; break; @@ -1201,16 +1516,18 @@ static void *scoring_thread_fn(void *arg) { } batches[bi].xs[batches[bi].len].str = snap[i]; batches[bi].xs[batches[bi].len].score = 0; + batches[bi].xs[batches[bi].len].idx = snap_idx[i]; batches[bi].len++; } if (!batch_ok) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(snap); free(filter); free(batches); continue; + free(snap); free(snap_idx); free(filter); free(batches); + shared_idx_release(refine_idx); continue; } - unsigned num_batches = count ? (unsigned)(bi + 1) : 0; + unsigned num_batches = snap_count ? (unsigned)(bi + 1) : 0; unsigned max_workers = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); size_t flen = strlen(filter); @@ -1240,8 +1557,8 @@ static void *scoring_thread_fn(void *arg) { pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; pthread_mutex_unlock(&s->score_req_mu); - free(snap); free(batches); free(filter); - continue; + free(snap); free(snap_idx); free(batches); free(filter); + shared_idx_release(refine_idx); continue; } /* Compact into flat array */ @@ -1262,6 +1579,17 @@ static void *scoring_thread_fn(void *arg) { size_t emit = (limit && limit < pos) ? limit : pos; + /* Build matched_idx array (all pos matches, not just top-K) for the + cache so a future subsuming query can refine-score only this set. */ + uint32_t *m_idx_buf = (pos && flat) ? malloc(pos * sizeof *m_idx_buf) : NULL; + if (m_idx_buf) for (size_t k = 0; k < pos; k++) m_idx_buf[k] = flat[k].idx; + + /* Cache the result. pool_gen = count (the pool size we actually scored). + For refine runs, count may be > refine_delta_from, so the new entry + supersedes the old one as a refinement source for the same query. */ + cache_insert(&s->cache, filter, count, flat, emit, m_idx_buf, pos); + free(m_idx_buf); + /* Clear active-filter marker before publishing results */ pthread_mutex_lock(&s->score_req_mu); free(s->score_current_filter); s->score_current_filter = NULL; @@ -1278,10 +1606,11 @@ static void *scoring_thread_fn(void *arg) { /* Increment gen so Elisp knows new results are available */ atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); - fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu\n", - filter, pos, count, emit); + fzf_log("scoring_thread: filter='%s' filtered=%zu total=%zu emit=%zu refine=%d scanned=%zu\n", + filter, pos, count, emit, refine ? 1 : 0, snap_count); - free(snap); free(batches); free(filter); + free(snap); free(snap_idx); free(batches); free(filter); + shared_idx_release(refine_idx); } fzf_log("scoring_thread EXIT\n"); @@ -1309,34 +1638,82 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, if (nargs > 2 && !env->eq(env, args[2], Qnil)) limit = (size_t)env->extract_integer(env, args[2]); - fzf_log("async_candidates: filter='%s' limit=%zu — dispatching to bg thread\n", - filter, limit); + /* Cache lookup. Exact-fresh hits skip scoring entirely; exact-stale + and prefix hits dispatch a refinement scoring run that scans only + the prior match set + candidates that arrived since. Misses fall + through to a normal full-pool scoring run. */ + ScoredStr *cached_top = NULL; + size_t cached_count = 0; + SharedIdx *cached_m_idx = NULL; + size_t cached_pool_gen = 0; + + bool exact_hit = cache_lookup_exact(&s->cache, filter, + &cached_top, &cached_count, + &cached_m_idx, &cached_pool_gen); + bool prefix_hit = false; + if (!exact_hit) + prefix_hit = cache_lookup_prefix(&s->cache, filter, + &cached_top, &cached_count, + &cached_m_idx, &cached_pool_gen); + + pthread_mutex_lock(&s->mu); + size_t current_pool = s->count; + pthread_mutex_unlock(&s->mu); + + bool exact_fresh = exact_hit && (cached_pool_gen == current_pool); + + fzf_log("async_candidates: filter='%s' limit=%zu pool=%zu hit=%s%s%s\n", + filter, limit, current_pool, + exact_fresh ? "exact-fresh" : "", + (exact_hit && !exact_fresh) ? "exact-stale" : "", + prefix_hit ? "prefix" : (!exact_hit ? "miss" : "")); /* Enqueue the new request. Only abort in-flight scoring if the filter actually changed — same-filter timer re-triggers must not interrupt a scoring run that is still working on the same query, which would cause - a livelock where scoring never completes on large candidate sets. */ + a livelock where scoring never completes on large candidate sets. + + On exact-fresh hit we skip enqueueing entirely — no work needed. */ pthread_mutex_lock(&s->score_req_mu); bool filter_changed = !(s->score_current_filter && strcmp(s->score_current_filter, filter) == 0 && s->score_current_limit == limit); if (filter_changed) atomic_store_explicit(&s->score_abort, true, memory_order_seq_cst); - free(s->score_req_filter); - s->score_req_filter = filter; /* scoring thread owns this now */ - s->score_req_limit = limit; - pthread_cond_signal(&s->score_req_cond); + + if (exact_fresh) { + /* No work to dispatch; release locally-owned filter + retained idx. */ + free(filter); + shared_idx_release(cached_m_idx); + } else { + free(s->score_req_filter); + shared_idx_release(s->score_req_refine_idx); + s->score_req_filter = filter; /* transfer */ + s->score_req_limit = limit; + s->score_req_refine_idx = cached_m_idx; /* transfer (NULL on miss) */ + s->score_req_refine_delta_from = cached_pool_gen; + pthread_cond_signal(&s->score_req_cond); + } pthread_mutex_unlock(&s->score_req_mu); - /* Copy latest scored results under lock so we can release quickly */ - pthread_mutex_lock(&s->score_res_mu); - size_t rcount = s->score_count; - ScoredStr *snap = rcount ? malloc(rcount * sizeof *snap) : NULL; - if (snap && s->score_results) - memcpy(snap, s->score_results, rcount * sizeof *snap); - else - rcount = 0; - pthread_mutex_unlock(&s->score_res_mu); + /* Determine display set. Cache hits return their (stale-but-useful) + top-K immediately; misses fall back to whatever the scorer last + published. */ + ScoredStr *snap = NULL; + size_t rcount = 0; + if (exact_hit || prefix_hit) { + snap = cached_top; /* ownership transferred from cache_lookup_*. */ + rcount = cached_count; + } else { + pthread_mutex_lock(&s->score_res_mu); + rcount = s->score_count; + snap = rcount ? malloc(rcount * sizeof *snap) : NULL; + if (snap && s->score_results) + memcpy(snap, s->score_results, rcount * sizeof *snap); + else + rcount = 0; + pthread_mutex_unlock(&s->score_res_mu); + } /* Resolve C-side highlight cap from defcustoms fzf-async-highlight and fzf-async-highlight-max-candidates. Both are read via symbol-value so From dc88f2b38fb5a0886c81e6cf098572c247139951 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 21:56:46 -0400 Subject: [PATCH 33/47] Add phase 2 cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit term-set subsumption + larger default Layered on top of phase 1 (commit ed25fc7). Three changes: 1. Term-set subsumption (replaces byte-prefix-only rule with byte- prefix OR term-set). Phase 1's subsumes(Q', Q) used byte-prefix matching: Q' subsumes Q iff Q' is a literal prefix of Q (and neither contains '|'). This caught extending a term, adding AND terms at the end, etc., but missed cases where Q' subsumes Q semantically: - Adding an AND term in non-prefix position: fo -> x fo - Term reordering: foo bar -> bar foo (same term set, different textual order) - Non-prefix negation/anchor: fo -> !x fo Phase 2 adds subsumes_pattern(P', P) operating on parsed fzf_pattern_t structures. Rule: every term-set in P' must have an equivalent term-set in P. Equivalent = same algorithm (fn function pointer), same inv flag, same case_sensitive flag, same strcmp(ptr). fzf semantics gotcha: within a term-set = OR (terms are alternatives); across term-sets = AND. So "foo bar" parses as 2 sets x 1 term (AND), and "foo | bar" parses as 1 set x 2 terms (OR). subsumes_pattern rejects any term-set with >1 term — these can never serve as refinement sources. cache_lookup_prefix now uses byte-prefix OR term-set — both rules are valid superset relations and we want the union. Best entry = most term-sets (most constraints = smallest match set = fastest refinement scan), with byte-length as tiebreaker. To avoid re-parsing on every iteration of the lookup scan, CacheEntry gained a fzf_pattern_t *parsed field populated on insert (and freed in cache_entry_free). fzf_parse_pattern mutates its input, so parse_query_for_cache strdups before parsing — the returned pattern is self-contained. 2. Default cache size 20 -> 40. Doubles the typing-trail kept in LRU. Helps backspace coverage: backspacing past N keystrokes still hits the LRU as long as those intermediate queries weren't evicted by unrelated lookups. C fallback in cache_init and async_start both bumped; the matching defcustom default change is in fzf-async. 3. Tests. 12 new ctests covering subsumes_pattern (extending via byte-prefix, adding at end/start, reorder, negation, OR rejection, distinct terms) and cache_lookup_prefix v2 paths (term-subset, reordered, picks-most-terms, skips-OR-in-query, skips-exact-match). Plus 2 new ERT tests: - fzf-native-async-cache-prefix-refinement-test: typing progression fo -> foo -> backspace to fo, verify backspace returns same set as initial (covers exact-cache-hit-on- backspace via larger LRU). - fzf-native-async-cache-term-reorder-test: foo bar and bar foo return identical sets (exercises subsumes_pattern term reordering end-to-end). Totals: 43 ctests pass (was 31, +12 phase 2); 33 ERT tests pass (was 31, +2 cache E2E). Measured behavior on 63.4M-candidate fzf-async-rg session typing a 6-AND-term query: each new term refines from the previous match set, final scan = 10,669 candidates (vs 63.4M for full scan, ~5,940x speedup). v2's most-terms preference correctly picks the most- restrictive prior entry as refinement source on every step. --- architecture.org | 82 +++++++++++++++++++ fzf-native-ctest.c | 193 ++++++++++++++++++++++++++++++++++++++++++++ fzf-native-module.c | 127 ++++++++++++++++++++++++++--- fzf-native-test.el | 63 ++++++++++++++- 4 files changed, 452 insertions(+), 13 deletions(-) diff --git a/architecture.org b/architecture.org index c6958c0..289390e 100644 --- a/architecture.org +++ b/architecture.org @@ -287,6 +287,10 @@ ready." │ score_count │ │ last_filtered │ │ last_total │ + ├─────────────────┤ + │ cache │ ← LRU result cache, own mutex + │ head, tail │ + │ count, max │ └─────────────────┘ #+end_src @@ -391,6 +395,84 @@ are still pulled. which serves as the "has any data arrived yet" signal but is not the cross-thread publish signal.) +** Result cache + +A per-session LRU cache sits between the dispatch path and the scoring +thread. Configured via the =fzf-async-cache-size= defcustom (read +once at session start; default 40 entries). + +*Data model*: + +#+begin_src + CacheEntry { + query : strdup'd filter string ← owned + pool_gen : s->count at time of scoring + top[K] : copy of top-K results published ← owned + m_idx : SharedIdx*, refcounted ← shared + parsed : fzf_pattern_t* (parsed query) ← owned + } + + Cache { + head, tail : doubly-linked list (MRU at head) + count, max + mu : mutex + } +#+end_src + +=SharedIdx= is a refcounted flexible-array struct of =uint32_t= +holding the *full set* of matched candidate indices for one scoring +run (not just the top-K). Lookup consumers retain in O(1) under the +cache mutex (atomic refcount bump — no memcpy); the last consumer +frees it. + +*Dispatch flow* (=fzf_native_async_candidates=, main thread): + +| Lookup result | Display | Scoring scheduled | +|----------------------+----------------------+-----------------------------------| +| Exact, pool_gen==now | Cached top-K | None (no work) | +| Exact, pool_gen1 term) and every term-set in Q' has an equivalent term-set + in Q. Captures: adding an AND term in non-prefix position + (=fo= → =x fo=), term reordering (=foo bar= → =bar foo=), + non-prefix negation (=fo= → =!x fo=). + +OR queries (containing =|=) are excluded from prefix lookups in both +directions — adding an OR alternate widens results unpredictably, so +prefix refinement is unsound. + +When multiple cached entries subsume Q, the one with the most +term-sets wins (= most constraints = smallest match set = fastest +refinement scan), with byte-length as a tiebreaker. + +*Refinement scoring*: When the scoring thread receives a refine +request, it builds the snap from =m_idx= ∪ =s->cands[delta_from..count]= +instead of the full =s->cands[]=. Typical match sets after 2-3 chars +are <1% of pool size, so refine scans drop by 100-1000× vs full +scans on large pools. + +*Critical-section discipline*: =cache_insert= pre-allocates everything +(strdup, malloc, parse_query) *before* taking the cache mutex. The +critical section is just pointer swaps + LRU list manipulation. +Evicted entries are freed after unlock. Lookups bump the SharedIdx +refcount under the mutex, then release it; the consumer holds the +=SharedIdx*= with no further locking. + +*fzf semantics gotcha*: within a term-set = OR (terms are +alternatives); across term-sets = AND. So =foo bar= parses as 2 sets +× 1 term (AND-of-two), and =foo | bar= parses as 1 set × 2 terms +(OR-of-two). The cache rejects any term-set with >1 term as an OR +query. + ** Memory and string ownership | Object | Owner | Lifetime | diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index 2835a74..1bd9e86 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -554,6 +554,185 @@ static void test_cache_pool_gen_distinguishes_stale(void) { cache_free(&c); } +/* ===================================================================== + * Result cache — phase 2: term-set subsumption + prefix lookup + * ===================================================================== */ + +static void test_subsumes_pattern_extending_term_via_byte_prefix(void) { + /* "fo" → "foo": same single-term query getting longer. Term-set rule + alone says NO (terms "fo" and "foo" aren't equivalent), but + cache_lookup_prefix uses byte-prefix OR term-set, so this still + captures via the byte-prefix path. Verify the byte-prefix subsumes() + directly. */ + CHECK(subsumes("fo", "foo") == true); +} + +static void test_subsumes_pattern_adding_term_at_end(void) { + /* "fo" → "fo bar": both rules agree. Verify term-set path. */ + fzf_pattern_t *p1 = parse_query_for_cache("fo"); + fzf_pattern_t *p2 = parse_query_for_cache("fo bar"); + CHECK(p1 && p2); + CHECK(subsumes_pattern(p1, p2) == true); + CHECK(subsumes_pattern(p2, p1) == false); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +static void test_subsumes_pattern_adding_term_at_start(void) { + /* "fo" → "x fo": v2-only case. Byte-prefix says NO (fo not prefix of + x fo), term-set says YES (fo's terms ⊆ x fo's terms). */ + fzf_pattern_t *p1 = parse_query_for_cache("fo"); + fzf_pattern_t *p2 = parse_query_for_cache("x fo"); + CHECK(p1 && p2); + CHECK(subsumes("fo", "x fo") == false); /* v1 misses */ + CHECK(subsumes_pattern(p1, p2) == true); /* v2 catches */ + CHECK(subsumes_pattern(p2, p1) == false); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +static void test_subsumes_pattern_term_reorder(void) { + /* "foo bar" and "bar foo" are semantically equivalent in fzf — same + term set, different textual order. Term-set rule sees mutual + subsumption; byte-prefix rule sees neither. */ + fzf_pattern_t *p1 = parse_query_for_cache("foo bar"); + fzf_pattern_t *p2 = parse_query_for_cache("bar foo"); + CHECK(p1 && p2); + CHECK(subsumes("foo bar", "bar foo") == false); + CHECK(subsumes("bar foo", "foo bar") == false); + CHECK(subsumes_pattern(p1, p2) == true); + CHECK(subsumes_pattern(p2, p1) == true); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +static void test_subsumes_pattern_negation_at_start(void) { + /* "fo" → "!x fo": adding a negation term in non-prefix position. + Term-set rule catches it; byte-prefix doesn't. */ + fzf_pattern_t *p1 = parse_query_for_cache("fo"); + fzf_pattern_t *p2 = parse_query_for_cache("!x fo"); + CHECK(p1 && p2); + CHECK(subsumes_pattern(p1, p2) == true); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +static void test_subsumes_pattern_or_query_rejected(void) { + /* "fo | bar" parses as ONE term-set with TWO terms (within a set = + OR; across sets = AND). subsumes_pattern rejects any term-set with + >1 term — it can never serve as a refinement source. */ + fzf_pattern_t *p1 = parse_query_for_cache("fo"); + fzf_pattern_t *p2 = parse_query_for_cache("fo | bar"); + CHECK(p1 && p2); + CHECK(p1->size == 1 && p1->ptr[0]->size == 1); /* "fo": 1 set, 1 term */ + CHECK(p2->size == 1 && p2->ptr[0]->size == 2); /* "fo|bar": 1 set, 2 terms */ + CHECK(subsumes_pattern(p1, p2) == false); + CHECK(subsumes_pattern(p2, p1) == false); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +static void test_subsumes_pattern_distinct_terms(void) { + /* "foo" and "bar" share no terms; neither subsumes the other. */ + fzf_pattern_t *p1 = parse_query_for_cache("foo"); + fzf_pattern_t *p2 = parse_query_for_cache("bar"); + CHECK(p1 && p2); + CHECK(subsumes_pattern(p1, p2) == false); + CHECK(subsumes_pattern(p2, p1) == false); + fzf_free_pattern(p1); + fzf_free_pattern(p2); +} + +/* Helper: insert a cache entry that has a non-NULL m_idx (so it's eligible + as a prefix-refinement source) using a single dummy match index. Tests + the lookup logic without caring about the actual indices. */ +static void cache_insert_eligible(Cache *c, const char *query, size_t pool_gen) { + uint32_t idx[1] = { 0 }; + cache_insert(c, query, pool_gen, NULL, 0, idx, 1); +} + +static void test_cache_lookup_prefix_v2_finds_term_subset(void) { + /* Cache has "fo". New query "x fo" should hit via term-set rule. */ + Cache c; + cache_init(&c, 20); + cache_insert_eligible(&c, "fo", 100); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "x fo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_gen == 100); + shared_idx_release(out_sidx); + free(out); + cache_free(&c); +} + +static void test_cache_lookup_prefix_v2_finds_reordered(void) { + /* Cache has "foo bar". New query "bar foo" should hit via term-set + mutual subsumption. We exclude exact-match entries from prefix + lookup, but "bar foo" != "foo bar" textually so it counts as + non-exact and the term-set rule picks it up. */ + Cache c; + cache_init(&c, 20); + cache_insert_eligible(&c, "foo bar", 100); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "bar foo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_gen == 100); + shared_idx_release(out_sidx); + free(out); + cache_free(&c); +} + +static void test_cache_lookup_prefix_picks_most_terms(void) { + /* Cache has "fo" (1 term) and "fo bar" (2 terms). New query + "fo bar baz" subsumes both. cache_lookup_prefix should prefer the + most-restricted entry — "fo bar" with 2 terms — over "fo". */ + Cache c; + cache_init(&c, 20); + cache_insert_eligible(&c, "fo", 100); + cache_insert_eligible(&c, "fo bar", 200); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "fo bar baz", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(out_gen == 200); /* "fo bar" entry wins */ + shared_idx_release(out_sidx); + free(out); + cache_free(&c); +} + +static void test_cache_lookup_prefix_skips_or_in_query(void) { + /* If the new query contains '|', prefix lookup short-circuits to false + (we never refine into an OR query). */ + Cache c; + cache_init(&c, 20); + cache_insert_eligible(&c, "fo", 100); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "fo | bar", &out, &out_count, &out_sidx, &out_gen) == false); + cache_free(&c); +} + +static void test_cache_lookup_prefix_skips_exact_match(void) { + /* Even if an entry's parsed pattern equals the new query's, we exclude + it from prefix lookup (that's what cache_lookup_exact is for). */ + Cache c; + cache_init(&c, 20); + cache_insert_eligible(&c, "fo bar", 100); + + ScoredStr *out = NULL; + SharedIdx *out_sidx = NULL; + size_t out_count = 0, out_gen = 0; + CHECK(cache_lookup_prefix(&c, "fo bar", &out, &out_count, &out_sidx, &out_gen) == false); + cache_free(&c); +} + /* ================================================================= */ int main(void) { @@ -595,6 +774,20 @@ int main(void) { RUN(test_cache_insert_zero_count); RUN(test_cache_pool_gen_distinguishes_stale); + printf("--- cache (phase 2: term-set subsumption) ---\n"); + RUN(test_subsumes_pattern_extending_term_via_byte_prefix); + RUN(test_subsumes_pattern_adding_term_at_end); + RUN(test_subsumes_pattern_adding_term_at_start); + RUN(test_subsumes_pattern_term_reorder); + RUN(test_subsumes_pattern_negation_at_start); + RUN(test_subsumes_pattern_or_query_rejected); + RUN(test_subsumes_pattern_distinct_terms); + RUN(test_cache_lookup_prefix_v2_finds_term_subset); + RUN(test_cache_lookup_prefix_v2_finds_reordered); + RUN(test_cache_lookup_prefix_picks_most_terms); + RUN(test_cache_lookup_prefix_skips_or_in_query); + RUN(test_cache_lookup_prefix_skips_exact_match); + if (failed == 0) { printf("\nAll tests passed.\n"); return 0; diff --git a/fzf-native-module.c b/fzf-native-module.c index a7cc08e..c7e9cee 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -780,6 +780,10 @@ typedef struct CacheEntry { ScoredStr *top; size_t top_count; SharedIdx *m_idx; + /* Parsed form of `query`, populated on insert. NULL when parsing failed + or for OR queries (which are excluded from prefix-refinement anyway). + Owned by the entry; freed in cache_entry_free. */ + fzf_pattern_t *parsed; } CacheEntry; typedef struct { @@ -794,7 +798,7 @@ static void cache_init(Cache *c, size_t max_entries) { pthread_mutex_init(&c->mu, NULL); c->head = c->tail = NULL; c->count = 0; - c->max_entries = max_entries ? max_entries : 20; + c->max_entries = max_entries ? max_entries : 40; } static void cache_entry_free(CacheEntry *e) { @@ -802,6 +806,7 @@ static void cache_entry_free(CacheEntry *e) { free(e->query); free(e->top); shared_idx_release(e->m_idx); + if (e->parsed) fzf_free_pattern(e->parsed); free(e); } @@ -832,9 +837,10 @@ static void cache_free(Cache *c) { } /* Q' subsumes Q iff neither contains '|' AND Q' is a byte-prefix of Q. - Captures: extending a term, adding AND terms, adding negations, adding - anchors. Conservatively rejects every query containing '|' — adding an - OR alternate widens results unpredictably. */ + Captures: extending a term (fo → foo), adding AND terms at end (fo → fo + bar), adding negations/anchors at end (fo → fo !x). Conservatively + rejects every query containing '|' — adding an OR alternate widens + results unpredictably. This is the v1 rule, kept as a fast-path. */ static bool subsumes(const char *q_prime, const char *q) { if (strchr(q_prime, '|') || strchr(q, '|')) return false; size_t lp = strlen(q_prime); @@ -844,6 +850,63 @@ static bool subsumes(const char *q_prime, const char *q) { return memcmp(q, q_prime, lp) == 0; } +/* Two parsed terms are equivalent iff they would match exactly the same + strings: same algorithm, same negation flag, same case sensitivity, same + text after fzf's prefix stripping. Both terms must have been parsed with + the same case mode (CaseIgnore in our usage), so `ptr` (the lowercased + token) is directly comparable. */ +static bool term_equiv(const fzf_term_t *a, const fzf_term_t *b) { + if (a->fn != b->fn) return false; + if (a->inv != b->inv) return false; + if (a->case_sensitive != b->case_sensitive) return false; + return strcmp(a->ptr, b->ptr) == 0; +} + +/* P' subsumes P (term-set rule) iff every term-set in P' has an equivalent + term-set in P. In fzf's model, term-sets are AND'd together; adding + more term-sets monotonically narrows the match set, so P (with all of + P''s term-sets plus possibly more) matches a subset of P''s candidates. + + Restricted to non-OR queries: any term-set with >1 term is an OR (e.g. + "fo | bar" parses as one set with two terms), and OR queries can never + serve as refinement sources because adding an OR alternate widens the + match set unpredictably. + + Catches v2-only cases: adding an AND term in non-prefix position (fo → + x fo), term reordering (fo bar → bar fo), non-prefix negation (fo → + !x fo). Empty P' (zero term-sets) trivially subsumes anything. */ +static bool subsumes_pattern(const fzf_pattern_t *p_prime, + const fzf_pattern_t *p) { + if (!p_prime || !p) return false; + /* Reject if either side has any OR-containing term-set. */ + for (size_t i = 0; i < p_prime->size; i++) + if (p_prime->ptr[i]->size != 1) return false; + for (size_t i = 0; i < p->size; i++) + if (p->ptr[i]->size != 1) return false; + /* Every single-term set in p_prime must equal some single-term set in p. */ + for (size_t i = 0; i < p_prime->size; i++) { + fzf_term_t *t_prime = &p_prime->ptr[i]->ptr[0]; + bool found = false; + for (size_t j = 0; j < p->size; j++) { + if (term_equiv(t_prime, &p->ptr[j]->ptr[0])) { found = true; break; } + } + if (!found) return false; + } + return true; +} + +/* Parse a query string into an fzf_pattern_t. Returns NULL if the query + is empty or parsing fails. fzf_parse_pattern mutates its input, so we + strdup first and free after — the returned pattern is self-contained. */ +static fzf_pattern_t *parse_query_for_cache(const char *query) { + if (!query || !*query) return NULL; + char *dup = strdup(query); + if (!dup) return NULL; + fzf_pattern_t *p = fzf_parse_pattern(CaseIgnore, false, dup, true); + free(dup); + return p; +} + /* Find an entry by exact query match. Caller holds c->mu. */ static CacheEntry *cache_find_locked(Cache *c, const char *query) { for (CacheEntry *e = c->head; e; e = e->next) @@ -879,24 +942,53 @@ static bool cache_lookup_exact(Cache *c, const char *query, return true; } -/* Prefix lookup: longest Q' that subsumes Q (and is not Q itself). Skips - entries with NULL m_idx — they can't serve as refinement sources. */ +/* Prefix lookup: most-constrained Q' that subsumes Q (and is not Q itself). + Uses byte-prefix OR term-set subsumption. Skips entries with NULL m_idx + (OR queries / empty match sets — can't serve as refinement sources). + + Best = the entry whose parsed pattern has the most terms. More terms = + more constraints = smaller match set = faster refinement scan. Falls + back to byte-prefix-length tiebreak when both have equal term counts + (or for entries whose parsed pattern is unavailable). */ static bool cache_lookup_prefix(Cache *c, const char *query, ScoredStr **out_top, size_t *out_top_count, SharedIdx **out_m_idx, size_t *out_pool_gen) { if (strchr(query, '|')) return false; /* fast reject */ + fzf_pattern_t *p_query = parse_query_for_cache(query); + /* If parse failed and query isn't empty, fall back to byte-prefix only. + Empty query has p_query == NULL but byte-prefix subsumes("", anything) + also returns true so the loop still works. */ + pthread_mutex_lock(&c->mu); CacheEntry *best = NULL; - size_t best_len = 0; + size_t best_terms = 0; + size_t best_len = 0; for (CacheEntry *e = c->head; e; e = e->next) { if (!e->m_idx) continue; if (strcmp(e->query, query) == 0) continue; - if (!subsumes(e->query, query)) continue; - size_t l = strlen(e->query); - if (l > best_len) { best = e; best_len = l; } + + bool match = subsumes(e->query, query) + || (p_query && subsumes_pattern(e->parsed, p_query)); + if (!match) continue; + + /* Term count = number of AND term-sets in the parsed pattern. More + sets = more constraints = smaller match set = better refinement + source. OR-containing entries have m_idx==NULL and were skipped. */ + size_t terms = e->parsed ? e->parsed->size : 0; + size_t len = strlen(e->query); + if (terms > best_terms || + (terms == best_terms && len > best_len)) { + best = e; + best_terms = terms; + best_len = len; + } + } + if (!best) { + pthread_mutex_unlock(&c->mu); + if (p_query) fzf_free_pattern(p_query); + return false; } - if (!best) { pthread_mutex_unlock(&c->mu); return false; } ScoredStr *top_copy = NULL; if (best->top_count) { @@ -910,6 +1002,7 @@ static bool cache_lookup_prefix(Cache *c, const char *query, if (best != c->head) { cache_unlink_locked(c, best); cache_push_head_locked(c, best); } pthread_mutex_unlock(&c->mu); + if (p_query) fzf_free_pattern(p_query); return true; } @@ -931,10 +1024,15 @@ static void cache_insert(Cache *c, const char *query, size_t pool_gen, } SharedIdx *sidx = (m_idx_src && m_idx_count && !strchr(query, '|')) ? shared_idx_alloc(m_idx_src, m_idx_count) : NULL; + /* Parse once on insert so cache_lookup_prefix doesn't pay parse cost on + every iteration of its scan loop. NULL is fine — entries with NULL + parsed only participate via the byte-prefix subsumption fallback. */ + fzf_pattern_t *parsed = parse_query_for_cache(query); if (!q_dup) { free(top_dup); shared_idx_release(sidx); + if (parsed) fzf_free_pattern(parsed); return; } @@ -945,16 +1043,19 @@ static void cache_insert(Cache *c, const char *query, size_t pool_gen, char *old_q = e->query; ScoredStr *old_top = e->top; SharedIdx *old_idx = e->m_idx; + fzf_pattern_t *old_parsed = e->parsed; e->query = q_dup; e->top = top_dup; e->top_count = top_dup ? top_count : 0; e->m_idx = sidx; + e->parsed = parsed; e->pool_gen = pool_gen; if (e != c->head) { cache_unlink_locked(c, e); cache_push_head_locked(c, e); } pthread_mutex_unlock(&c->mu); free(old_q); free(old_top); shared_idx_release(old_idx); + if (old_parsed) fzf_free_pattern(old_parsed); return; } @@ -965,12 +1066,14 @@ static void cache_insert(Cache *c, const char *query, size_t pool_gen, free(q_dup); free(top_dup); shared_idx_release(sidx); + if (parsed) fzf_free_pattern(parsed); return; } ne->query = q_dup; ne->top = top_dup; ne->top_count = top_dup ? top_count : 0; ne->m_idx = sidx; + ne->parsed = parsed; ne->pool_gen = pool_gen; cache_push_head_locked(c, ne); @@ -1297,7 +1400,7 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, } { - size_t cache_max = 20; + size_t cache_max = 40; emacs_value sym = env->intern(env, "fzf-async-cache-size"); emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) diff --git a/fzf-native-test.el b/fzf-native-test.el index 3324629..dcf1bc6 100644 --- a/fzf-native-test.el +++ b/fzf-native-test.el @@ -177,7 +177,7 @@ ;; the same as "did not match" and the candidate is silently dropped. (defconst fzf-native-test--bad-bytes - (string-as-multibyte ";; Copyright 2022 Jo Be") + (string-as-multibyte ";; Copyright 2022 Jo Be�����") "Raw-byte string used as a reproducer for the `unicode-string-p' bug. Note: on Emacs 30+ this WILL coerce successfully through `encode-coding-string', so it scores like any other string. The tests @@ -364,3 +364,64 @@ currently being scored. Stats are only written on completion, so (should done))) (fzf-native-async-stop handle)))) +(ert-deftest fzf-native-async-cache-prefix-refinement-test () + "Cache returns consistent results across a typing progression and on +backspace. Setup: a small corpus where 'fo'/'foo'/'food' produce +predictably-different result sets. We type the progression, verify +each query's results, then backspace back to 'fo' and verify it +returns the same set as the original 'fo' call. + +This exercises: +- Phase-1 exact lookup (each first call inserts; second call hits) +- Phase-2 prefix refinement (typing extends matched_idx) +- Backspace coverage (LRU keeps prior queries)" + (skip-unless (fboundp 'fzf-native-async-start)) + (let ((handle (fzf-native-async-start + "printf '%s\\n' food foo foobar fool bar baz"))) + (unwind-protect + (progn + (fzf-native-test--wait-for-data handle) + (let ((r-fo-1 (sort (copy-sequence + (fzf-native-test--wait-for-scoring handle "fo")) + #'string<))) + (should (member "foo" r-fo-1)) + (should (member "food" r-fo-1)) + (should (member "foobar" r-fo-1)) + (should (member "fool" r-fo-1)) + (should-not (member "bar" r-fo-1)) + ;; Type "foo": narrower than "fo" — refinement scenario + (let ((r-foo (fzf-native-test--wait-for-scoring handle "foo"))) + (should (member "foo" r-foo)) + (should (member "food" r-foo)) + (should (member "foobar" r-foo)) + ;; "fool" doesn't fuzzy-match "foo" cleanly; just check non-foo + ;; candidates are absent + (should-not (member "bar" r-foo)) + (should-not (member "baz" r-foo))) + ;; Backspace to "fo" — should hit cached entry, return same set + (let ((r-fo-2 (sort (copy-sequence + (fzf-native-async-candidates handle "fo")) + #'string<))) + (should (equal r-fo-2 r-fo-1))))) + (fzf-native-async-stop handle)))) + +(ert-deftest fzf-native-async-cache-term-reorder-test () + "Term reordering: \"foo bar\" and \"bar foo\" are semantically equal +in fzf and the cache should treat them so via term-set subsumption +(v2). Both queries should return the same candidates." + (skip-unless (fboundp 'fzf-native-async-start)) + (let ((handle (fzf-native-async-start + "printf '%s\\n' foobar fooXbar bar foo barfoo barXfoo"))) + (unwind-protect + (progn + (fzf-native-test--wait-for-data handle) + (let ((r1 (sort (copy-sequence + (fzf-native-test--wait-for-scoring handle "foo bar")) + #'string<)) + (r2 (sort (copy-sequence + (fzf-native-test--wait-for-scoring handle "bar foo")) + #'string<))) + (should r1) + (should (equal r1 r2)))) + (fzf-native-async-stop handle)))) + From 02368af86ff37d733cb25ba3197ea065e276dc78 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 02:04:29 +0000 Subject: [PATCH 34/47] Update binaries for all platforms --- bin/Darwin/arm64/fzf-native-module.so | Bin 55384 -> 71976 bytes bin/FreeBSD/fzf-native-module.so | Bin 52008 -> 56848 bytes bin/Linux/fzf-native-module.so | Bin 61680 -> 65880 bytes bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 31232 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 8d443aa399c5309349c41ae7a0a71be64a223379..aa953b43dd9352ec794dc085420b42116e525901 100755 GIT binary patch delta 15110 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 delta 11086 zcmai43w%>W)}OgIp=nzBOp`V(ZBw8xpuFFYrXWy2d2|aRsI-XETBINk%R>@C-9lL; zOts5n<)J@qvg%e_psb2PR~Hr6A})*TD+P4x*CL?RZFrdPe{OC|sQvivuXE@8XU?3N zIdkUBnVVL>t~pyH@5qU?vs)j#BX6A4tJtw~QdGjD{;CMcpC>%(_#cay&wE7mUFk3J zX#IKLm0~MT7J8l-6R}opVowRVvrW%siC zQ5kP9WlUB{%DN%-6;DdNBsUX(mZnp7&7Vz^C&e+fnU#3)xRp1X8!KFL(w#ub5H^@G zdju+y-({>Gm?j)c`0tF75^6qStN~>+;A6 zF0+|cdU{&D`x&c?VOFn^upHw$20g)nbXrqw$|WJFB{|MIBc8L|48Ga{{C>OY(7n(a4y`Jb7@zoSM;&zgiW`gQ4P zqxdj-o>XcQsfGvQb|g6@jXm$ix(UwK8RMP5+@0fWTkyB)H%;Q8;a4dOSn8ORDusE5TjJOJS|yGAT`SDjeF@00l{QDBc1HO8RW2Fw0l z4l)!^5c58hGSLYGYOsX%7M~dJgGKf;jmEp!^fVOZHN_A7cMPj+#gf@ItggJ9)tifa zi-frc>niSn9!9<^IV{H))XxPS`@H11TTF~C3HG5?lL=hnXl$+2-zb*GWkTStxGYVB z#oAkho8!cVxU_aKb5Mij-rgN1?D;}+u1dt3XIZ_<=+@t?qp|qc!(r|dsjL*2F&|h$ z67~17c`NHsqv7sPtUF_VJx2(Zwz|#zyxXQMq}$P zCyWKERkm)NOBre>Bl2AIm>&9Nm6dF z8cVhDrlLk;pQKvhIatjD`-I_!R&K;D)54lAs&0&_k+wk*t zep`S6)ZC_l<+tjyoV*SH#xD53?1JCcZU6_mXwce4gLeMLF8B>S|L-L$>_VfxdOB?F zp0It3bSyzm7Y&Borh|>`Dh<$R*=_ibD-*EYx<%Z#YtY4j3^!f*ZzgcOivgu~<{utl zbO*XI_rIY1qpnx}T z3b+0l~$Pzp?KvqXo_P_DY?=0iISUKA1isR>m%fLgt1L& zIMqpY0rA#>cyYK-ime_j)`+&@6s=m#5Ifv@^nlXb=&Dn4lk0UQk9E}`PsYIcAbZ|U zqQhAosf~u>RCr=sFCqV2&yIQqTaV)8tFhdh=GcWYx1M=*Y*XHeamQp`p4KBD7ZAbNXB{O)EAD$p}1$& zj31Eca4+;vn(E?EXJ@pyM%Q5ttIQ<%bF5yD#j_*_DHA-0Ix?NE7IpsxU%op6W~_b zl39f{ggIyi?i;>3s%x9UoGGNKJJK9EsB1wTI^MxBs79<3b$mgQ7w2+Dh#v+_)!q%o z<%+Qb@1|q7MyE!$H`e9g;_yzjTV8O+H$gsiy)?*qb+HbYh+teM^r#C#tQhLNft$~N zWy#eha~I7von&C&(4DDzv$;(-*XqqdI7&-^n^FzklNg?dY%P2E5W|)Ib zO?Ogk>YBh!-Iz*kjJWXJi3!=JVP;b?!zq|)uJd~7WG80sAPvwxnFL-$uuTTsh!Cp% z!mA9Hv0-$NrX4vS18_fU^%ih;2={YC(KtVU`=Lfokoab)d>+^UMa~OtD1gxnJ+T`z z;-+TM`Lt~_ux7#7y6&x3uL(T=yg$ySma;4WA*NV#vZ%0{xsbR z3@9V*Sz(;Vk6~vZCoR!kBtmUB2>E&p)&|2d#M3MwtRD)$Wb6>WG8kgivUHSrcCDMa^9>WL`z#8b2eDJQ1lEk)%pBKn-?@$}WfrM<{7r!#42JN=ZmeS< zigDxkCByVQub~TfjSi=rdupI52B65Cm9Ih*67aadG{w2a(`-S5`Qyqz>sO zXu+Va&X=9%-}QGe9y6nX{~~&fN=hlqvwG{IbQQaCXZ6Tt*MHFR9P~gI&A1$9W_S;%TL>`Y=aqAmqj9hAzEBaM^9dsSyi1{}5L33jCZ7dPtJl zl{Q)D>i4rRzX-g0F<--)GI42?aiXdMf1e483;h38&{+dw>gd#a6s6#Dl0uqOrr`Ie z`*m#CHLTPZdUAX?=aA0fBT`S~g`xf{{1wcqeFgVi^c_mWN>xY2R~T~Gsk_AoqvKQ8 zVIF&6RHU?Bio)Vt>u*t594SmcCe7BB!lALEKvTNz8{(laJ=j!lc8A|oKgfKIanJ{@ z7)U1jk4D~=M=1E*m;WtzX_L2aaJswG;*Mh91`Vg(9H<24+d#ko?B{($TcMPF-Pjcu zmL(obHAfadj7dSj6*;z7tViRBLg$~I$#v2`Bl@Y%9@~UpG*fuH@MFrxKSP4X)ye%9tQ;u3w*Sp1A^Hl$3T*| z>5h@*oDD002O9uP`~AkcY2crVc2m$k7jh=U4NtN;Az1Ol>);L(_>R073Bd;2|ELk4 zxaJ@?$vg6fBfkb`5Cjt|fO{;2V4d1gl9nxZU-#vg!G(_S`Rdkew|Z?`F;)2KMlur| zuf4!q@LiOX1{6x|)QG8o?#)^htb|R_j1?L_*8bz;r@x@a_=?H!MUfg_116y-k=vdn zT}lqG+QHwc4){mpH2dmnLBozib3U{uxh+(VK9abh**vgvcO7iFa2V{+_S561J^0e} zE*QM9tu~yS)ZjeuMfj7eh`HzKSY52@GR?^2kiwveHmICF{pi-LY1^_eVNWKv9_l_24MJsXDUd?#)(wl*-Pw{#ZySoopQmcRSn`i&LX~L&6?iNl|?g zPhP$mA0I2>YxYUz=ANK)uh~v7fG;w~?Ax6?*j&M{V8Br6aT;JecB7Wn<;sR;C;Z*_ zu$`nxcI92p^7TJ&*ttd0S8S6K_B6|S*S_L0_4|s4*6+iB_7xAW-^Ps&LZ@X{4uI!9sFMy2(!|@GU1<%fG}8y>w=fX8 zJDfwXkW29xZUpTvi`>;3J$(-+yOA6f;H4$J7rB24ZIWXP04>4YltajD$}z*=A*V(g zkkgu`Ukn;{7Y0Gr>^aSyo(D8etL^dAt)*J$Ebvf_Rse6;1izGyL70(p^B2F`6U!OS`;!uuG8&n48`YDC7b0MTMg*oQo8@L5CR+!aYV1LTmMtphTfm>C+ zkyB`%3Eb`!KM27wgX4wPwiOe>+ACXdI(TT-Nyys`_fF9H(2A`UBW&%)hQO&U-w4Cd zp_5=F3U4UaBbBeU?QF(b)B>&ip?H1$b{N_=c$5qtRxy=#uPDLUMawva2Un1VBc{*S z7rwctT+)3s4ZImV1bqIb+z7s6Xes&P`qP^3Y>c8dh63uCtUDO)48JECuiv9Z5F@J?O~fIhg4j`ve2%e_TS?i&*tXl^ntr~XTwhNaL@V93+KmI-XHank932ALTHU zsjj~%gu(R=a_ZBMnV@2qv3* zY5mR&@ywJrYzCH^xIor5Cqsu*WZg&U(Ygwqg*o)vocaaaP?CSV(wu=UL3*@zW04yn zWj=azV;@qCp~GG6dpM5FlIr@Bbr088~gFKLNHh<#Kg_9m;}Qe(n*+(bs@oeZa-SG&Q-qzf|o!r+xX2G``SN` zY43oey>e-UWuJp_(ctJ%xl768UDJ>UW6BhFhXqWc9K!BUa@jF}a)dL~XbjPM3=|GMI8^+4mwC>E zD1wHgig5QQS3aCra1QSWivicAZos6FI()si4T8JJIlzery#3X@m%#`*TzujWqB@yBZ|g{4|< zsdYRwne$I-KVLkp8y_QjFSSZfz9W3ACW@DrF415uE{lZHd<}l3yj;7obO`5rgman7 zGh0}fFVogprcAXO!A2adKg|@7J%*POvT{2>X2X>-#kdW^9hnP~9uAkM<@x9N7&tsWlU0K#0 zC_e;VIwWw}%d^JPJ<05P31$8}x7lSuU%P>6jo{!%5`LDj@(YXEwM$$q`yJmXa@H)8 zX7v*_X^uRXqk^RU|9wo(bVMTCB^DkdeJQRuJ$+898a z1L(v6IwgR{uP7aIG6U$W02=1m!7~iEwGM!orGrLz+(C~IpeF^;Qv+!1s}BCW0D4XU zJuiU1zn!*W_6yqq=!XL6hXd%P0raB*^y&b5Z2-L?fZh~9KNUc41#N3r`}Tl}odNW- z0rc(w`ndr5U;zDM0PPB(Uk#vNzeyV+4&MY3fA!O~ZJSSNlZ%QM7cD_r$&%T<$*lc> z{Uc1pe?*9jTheR@lQsC^OcRbo6>U1k@Pn$7+W(t_aNq-e7K$I}cVJ)Avo{Fs;2ZKo zn6$S$lg}dJEQ?^8MFyrVGBCc^z+_Df)2vKl_yEP^$~4BCa0P7b$EHXvSxkF9i*>8S ze?>HmVEpn(2pr9{FOOziH-@n#bC{-VK7=n~Qs{$>J@F9J)GcPbWigXh7BRW0h;eK~ z4gT}ts;0J^dl|>4BY@sRYcFqI9Naw_1DuHzim~>8CXx@}_GiZPlKbj7<8GuDq$r8; zF-QxMHX%KSbP}l*DM4mD7ikVs5mFh_Hl)2sFC)E)bQ0-nq#uxeMT)2jWDN zC-DXB0{#&M>fM1aUK;jCm@rspf znueWKYCOujNqJv}kQZg1luAPE8|ht+H*~dI67A<{gWzfb|s(pQ^ijf}`bUhpT)Vo*I1e--KRn>m``4@q;Mg7MuYKeC64lEsupT_s_y#Ngr4}aYKNAagImrKx`5WTT3!(L!aoSU zuth06R%PkNv!wFBRn5Xd#O12(-FRu_o8gP{w}rOxh(BuI4BIcgELTMY@njogpTl^= z(0L_XAU!QAzD)mfgXg-6??iY{j}c*`!jA}>p1~?^fGN>4UBye%{rDjjrw{Ps$5p(I zF#2b^)dG*oa74u|!~6|CQSpW0e*CqHTUFem;%%e+^*$A!$nj$%Oo#^DG~SQ;T>*5IK7bw|Itl|v13St_qz!(b_$7bwVM!Q%}9i6+=LkW5s7ep zo|_Sd+^i}@&wJRGE%G-UPZdb{@PqW+ zqvAVBM|et9{PHqCwyD^&+>f`bm`(KKgDNhc>c@vwyuQ?rKk(z8OtaKqa8_klq6)mE z;tR{FKGgH+6ME6&;dumBMgx5TR!Pr#6)#%g$J4#IIk z0RH?>btinR6F$`mf7b~kYP&^VFdRXkeABHsk7eCjz{YpNb2{NAo$!iIczxAt-EjzP zuR7J8&)`R@EMdHVOhYFoT9-*x1H<{B`GhJe2S`N)=N{)|-B*T@Hrx%zde z8_qQcobAdnpbQF>`Y{ReB;`~mi)cTu!x%dgku0%L{GVZ}t+*Gel;@$f5A|DGxUzWl zgZ&;UU9@V+gIOy|S?b}nRk0!CTk6lhaG+1t?EV#H-Cr#X+dO5-1LeQ|GUi|F@9E#? zqH$43-`%cX@}3^~j&;Vtp(CGf{@g8p{pE^116N;|@OEnJp>Yqe(W@VQ?7;Yj$~N=l z8!eWHzxiZObMjAL|FYK>*E(wW*V8Aw!ZKUsr%5ul-t9ysO^rE44)Lq}QmVdL; z8`lKe20U{AYW?a_xjToKjL!M$yKmyo$lKm%+gq_=X!mn%Jzf}l)EYHfm;S;lVGjhg zT-|i>y|4beIr7^>-h_8cU!P{4d~Dc1zmxX7bufHPi%5K`rPn(jH?``&n)LJrd0WoR z-!H5_zWwkj7BM5_T#oUMkFuYAaO>gocP8rhd^`eDPr zLJn0{EZV%GO@H=_S7k@kF1d=w@`A~QIsHBx`1W5;^n9hyb9a^P4bc|!%16F`zW7P` y!{`2W&-rh9V{rDvj)9x9dB`F+cl>^&#KZSHpX=pq5lqWu9W-cTiOw5Tiq&}wNHl(d zH*`L5ygfZ>)%mPAoragI_`8l*oaS_*BqCCPYDRcXRA3Y&mKtml4K|4en~t+JmgHOi zzx@!kOS#_`-WiGNQU>Gk2XwB;IJf3U%Tp1Sf$aXMUY4Q!IhjAR`BUc4+xWA9Kg;;@ zFk2Gc%knWx!}BahE^=fU+X>`u{#?WMNB8%x_*(CPO()WWXtiK>Ya@zow@Z7g`yhbO|a{|xX%>l2pSS0))5(0XHlqT@;qC+LM zI*=?RBZE?hQohk~V*s-Ro*IVF5Cdp!Hvq3Bb>I!#r3HILi-Nd0-bG6Mn+~K>MubNE zsNsTk6-km+G+Z5qcM}aagyG`}_o5;#Ea7G$(xz8=d(wmmo$eG3l!xVS0M12ylb%n^ zoCFwnSIHeVuup;00E$r$#xF^R=zbRo>0zRKRx}VR8i=7n2sP*!HJqjD{2I|wnmTG= zL0Ct78}+=S=fBIMw=ex$B(&_(6ISX(x>JZQA*@3-A)$huddAhlDp0-D@b>VAMLmsS z_+KM1ei}i`!+J&2MZ>2=MI~VcRCJUYs0hPv5e?YFI+`syn)_4)C@-u-14TV2!|;&;ZxXoInEPyz&=OXlw-`aw zb9#eL(Q=fKM55@BQBSj&%1WUtb43E?nvb|zOtDzr6Qx&0LZT=zOm9c}k7%GatV6R! zN2PDrW1VhifliU^e5cOrN~in`i)57^K>vg`NYyD1LabKFC$JjMM@T!w7^tPr_>E@I zc2-A@M(hUsZp6=l-&Fi${5<&O;x`w+`S{(A-yLjTmk+#45u=|G;t`pKAC;sqLnqQW z&{X`4eq|xP1V6Gcx8O%LQ|N}@B>ZL(BSJ?b0!q3vLGbA1C}{*3>fQnY_7k+1pn0Gq zf7(=Y*v?madGA1M5q=P&bQ^v&&=mX@;x`aK2%Q^5(koI@Bai^*2*#KpVo4Zr_|bHd zB~QkWf|yLcW!?&YI7cBJGEVb!hCgZlOUG{ze%bhC;%9`3B0^F(2PfP7>`d18rOqAg z0*=|axx1yu&dt5X^#qb=%8Z??E%a`SNm3Nz0|eif#aRd4I#h6D1x*r^NGJSAKa9{F zkp%oA@Vg$rQTUC-F9W~+_>ta>!HAU)@4au?J<{63HOp44VOjA#B9|^(RZ#POAl<^MdL+VNwfQPEwG<5BCACrK z$xoq)G=lu09wVG4+X%S=C%rUAPiYGT9v6n^0jKu)tcn>WR$5_d*es@M6Q0U*>JQC? zr6c7@@$FpH`X?%1yZNKzUM&3D+$( zA3v6|sew$R7SQb`Jj;X!Ocgmy_(>B^>TiT%6P{y$pxeEs1g9yX!GupU;khQ9w9*J! zrjE`uK+qK?oa~bk@o)-$!HlN!Gx31G(v$1UnpD|o>pOM zm@K3b8cq0O0|ad{;dv%J#niwO6JC2-XUJ)2Tj->j0aF5*cq25JaI#rOXf)x5wWYL^ zCfu}HN=+u5ET|DQ;Cy9Jk4Qs|&|)e;o2wC8O*oZl3up8gVTO4l#Ad<`15J4BHQdYh zSpq2}TuYE#I>eueCfvNYrkL;;Q~p#FPMe|;(oHyRs$%?-lx0dV8~{p|O*m~QM#wec ze9Ot|54@%A-SLVr_%789Om!dp%F023~KturLifhOE$!Uvh~Si))jQ312x zl3*%8o|X|3O?a{af~J`8AtpT4gikl&=_cG|_6xI23FM_3K{ny!Fc~4&gpVSJsU#_t zt{ixXfMZuv*)Pr#pZ{R~$R%qZSiNlIvL(w_EFZac<%7#>s{1!a(%>z~dO`_X zCFvW`Rt44(`jD~{hYC;)bOq=FhJ2vgABUJ=VjDn{K+~VVfdsSyv;g!ZXa(z-Jj9aA z((tTcw#c{*rp+) zEWfbVhxE66!~TiqFYJdQL#%ysB=@@RYKP7pY>v*+uwN|pN7vY`)~Q{hr|apYhct&-(^>Y=QPJftNm^}3^*v{;6Sb!!$9ClS7+yhFv?0w9 zHF$g-c0~K59*KN3BBH{A+_TUZs|S$-hmZa9_y@>J{W^%URkyLDLkD)r0^0)iUhMzB zFvqY2*9I%HJE>gAh)~C9RC7S7_oOf>d4JsdM5NC}dO{w%eptWgVnlQD*py+zqN@;n zd>LCkEIImP%8<{V1{w)vS+I^B9X71vSVW5-l%yZo{lgOCzoLFX5~wc^3GR*PGbJQM zQo=PMmm~2Q*3|^+eUHHrycCV{T_iMZ!I4&=hu24Rif#q+ z%?`G2M8D`h(A-2nJ2PTnbPl5Feikz_H974ubgQtltWGX}0h+!RLz4Y{50+ze>bSm0 zbVrQq_ZZ6?x!hX6lhu!uEh>v0HPE_rCmTCzaj#!_#+2r@&a{wAxtJDmrd`sKHn3Ml zT|dQ!btWrc$z`XCTHMNIt@R;Ea+Zb)$GDYqj?mns#Iit9vP-=^DP8uD%RCbb34DR9 zcG*#0V;voBv3~quO-$-oi}j5MSXSDI9_0_{kWLjHs+t!Z`ZS-H)L7AUeE5iVV3=d_2^bvpm3~9IjlXo zo;BQ%Vx6>}{d7Zy^?sF&96M)JG6e5d6J<3j2{Bp4D9X}9p%8%HZ9tX|bW_=DW4HDF zwTLoqtd`3{Zgo|lFiIP;jw6lQrXrR*?zHt(A-iGxs(wkZExbl&1FGxS#x;UVP}sil z+pN6`+4Ko_TlcJGH4_e7Bi6Eq((kvX4yiexKFeawy`N3F@m*`T0(Rxb-7!zzhn_;T zUGHOoiSbKYSEF*}E3Jj7ze`4vB<)UwkdVc#T$OzXZ3`W@I~Fht=Z~`Y zFGUXAEXc~Yj`@yTZqAjJpIyl9ANRvY7~}i}vf^y=sGD+u9AV=prCa;?Siz+0t=T@d zW70b7E2~-LZCtIaIvY_%0BF<}_S|G=@+BDFYgt>`ICIvgH?z)D z`dJ@a#YRlYwtUAbrzEg#QxYt@5X-dmWld8Ox>bCDIZ?g}-Mj!(iR!;EVKJG5tcy$7 z=uGE;?In`b{0>h~k?Wn67^4JQ%c&Z*vV=X6In5e(5BqoK;K{e$LsGinRxY8d)v{8j ztpHLMus`w)@+;1dG5GJ=nxo-(lwaKHS`tctWoC`Pt{2w3tj3}}w{kXgb3tjKC>4Fm z^0G&=9M-{J_F-1d(h#CjYCG4`iKB0Zes5{Zpq%mm*PXYZvWufdQW*rxNQ96E+02! z1!Yd;y4xiUi{0&y8qkQP=O5`SXUnp4`qcf?lrgnk#{TR;c1E|+Z5c@F{&APyXI-a` z?bF-b0AC#bE@xqV&_G{y$5dzEq3@e2&ZUa|UFN|JM}|$u*?^n`c5>>7zQfJA^7LFe zdaf%UAXiSG|2<}^r$Em!NY62qZO(CCKkMkV49*g?@&r6twDQ(bl<&nZ=L{LXS?{b{ zIS=K*Y?j01d;v3$T<`1Er(H@r;r5Sv*3Vqd6w7Y5#yMu#XXqPOwnLEd7QGc+#;Bqa zRm|3_Xnmg@caF29u*hknSH0FIE~tjA9Cn00LrxzInXFjc>O-**VULDuJ-(Kwf4e*z z{yUFbo!p5V#13xt9;&A=tDM#&Hq$7_vF#1)xoPpSQ`>=4j)6MxL~CskyW|>V{c{oPJ!7Hu$vjp(W3=_# zLiX~E4C}Bw_RWmJJ!c)drYFv7VSbv}&Y8ol*@etKvww0Ea%lJF(KwtH(1^PZ{6UG; zyhP(#wr^&C@1GB&#BXq=xeois<(u1R6m49&R*m1&xZ6V3NNDq?F69s!ntCu4GVl+f8A)bk& z&ZnISu?lKY-MC%$g{&Krv?p-{z#eWd?T;0Sie$fMJGZ>&S-Gd5cM7f*u*2t|M7b7+ zytGGlq%!Kp3XN{@5vkNJ!@+bbA?*UFtVUJcPXzY);DT%0qrFs)HYp8_(s-Ld zgPX60LIi#UCP%rj&yGx5G^z$eHdBj19MUb=-l;TWwxa$6pTx*CThCQAl20s{Vwa+4=lzBESlErnQrfCPO&HQE@BP7$dIs1x zvy#1C^%9Qv$Qy6YMw54&nxq!>B<$XUP3<0TYtp7y(A>dOcFAL(ebwcEH>m-2LBIhF z*giktu*a1qH3Fh;HGxv_DDNg|bkX4QZAglh3MUawl?uno{vHZs2-i35l3O{g-Fq9U zZc!fqvadc5$=~6INl(k=X@y;orsWL4WJv$PNd4o|5$Byf6Jq2O1b@1sPm&!y!p!X> zP1Kfx@zOm0hH7O0O<|C~vP~1Z~pw#cfWJuaLAn+uQBd@0{R5%g^_V0sc zP6kfiGvsifBwZ?+j)W>Cz`x|nUmHOg;NnPy25Ys@(OrYUpidqi4e21;Hc&Fd0H~S*DHa(UGXo_J!AhTax%1P7~g#zeH zU*4DVdS9MH@8Q*HM;1URD_|-j?ESM?=A5w|Kktv+YEcPkrfJ^q(SV@ZqFK&&3cdk!`@ zU^)wURIiO@{U(>{jfIn>Ekr(O)~678iUpm^vwEghoNN1~+B zL@S8~pTxtN_6<&(dYy$4Eg=$E75D_uN*p(8-4pqXlfv6ws$DKSA}b9pdk$njA6lDh zlhvYH*}s*hRW3Ur+jG2)vJ#`O+zzt(2w%I(Y#T}zM!1y>Xe>6UT?EU%T~tge0-g?4 zXu!tRNYm!Vw56Sb%s`AJq&2y0ffxrXh5`R3 zSaAFVTGU`K9feij60+ON1|y=~j^rTy6$y3DrEV(LW&lB>+sP^!$JQ7WD2mcjk%X~0 z>`xqat56u_zGC#E+@)+Qp)RVBwz3TcDO!mr>6**$Ezs^lpJ_yypbp!YXsf|q_6AZt z$`#k8u;^;6~(g zx{yie9rdpC7F6QfxLqpD_b8WPU*E2!9c)oGH~kB6exhTM@QgBcwQV+je=2+K=6>Go zq{n7GtOpUsu4Uo}$Ok4nI{1~Q2 zS$SIOZd^s7#8)y#f;=HQfxprxkzXP`q(&2 z9gC%NS^E>8I2^5MIvt^p$+;AD6(;&xE%veqUJVSLcYX>~gX&)Ua_~H6KEK>O5sOVZ zZ};V(Ban2!9kH>|rDkHCRmtkSO05${My8z1P(1Q@)Vp8+F%KPh0T?>9!!-5Y$}2F- zR{M_o;nKK5$1o|)vDi^0#3}b9w*&Wh(YTR2bl8(|zVP%PO&z+A(U|6qPg_ks3BxpP zue1R&y3&5yX7RM}Y4cv2H|2bA87~#(=2`g`L9Nm*VK2QN!hvRbl>d<7Ds3{Y8Fk)v zzG$=?=8$9#YJUa|PKUqYR$Ao!O_4A`jc$LE&E-4T(i~SV^qn{_bhdDVKBKSl8C54G zk@|do6ITejiK03Dgapp#5c5~~9!%YIlVaVwoCE>I+S{gB{~x_7eMr}TZU=D-_jWE- zv#}Be4wcse`9?>!YZLWIqc(;j%1L?i!IRKGlk1OV^y!^czP;SaM#8-kQ%b@l$CQ?# z&}^>_$)2(w?2klKM7bJ#1!@5UQ-RvCkk4s9L6(?FSats1pKDzbRz6k{-Dki%!qO~8 z41y;gKhWIY-k(Zf75?nTs_>|HLh{O&Tt(^Ki(A>RJCyd)e~?FowblUR0v*3SDVA&6 zdCiYjVN-X>%A!g@`tDmnU4iJkLr4J~D$}9DhX7U3b~N3k=HQMNWhcw(0&I(??4|!h zji|P?6DF@gR>yuQ`!;!PmZBbVs7}uKsOSf{d?*mIm(gBt8UbCB1}9*M`KAgmI+xNA zOynwy+E?=&O?f8p)vjhCL| zj0YR@(6l4S-)Qe&0tnmR+<(H{ITs-U$vsgjI%j`- zmhDE_R`ewzi8soLMW4v(Ca76KtftK-E6Xd8Rn$kirZ$!QLm=>4P5?n!=O1Z}(ciJh+ zB?{Od-;aa_IiuD7R9)$Z@S5t$1i0+ZW4JF4PUi*bPJ59T&Ou|aKLAfzz^!tXMh{7! z3#WV30`ea1rFfMFtv%4T>$}BNZvjrmU0s_foXG1vgq)PsnJJ>0=KE+BotT9pV%sJs z0UF|LKm_`aSd{@)?x8e@se;-q`y5Pg)+HKdlw}FBstLi1ztf_-6ufwu=r?Icn(ds3 z=FqrH&4mMdf@&%3+7hywSFbV;+Tr1zpi7k#u>W}~T^W$uURuoLgJK1c4@xpaMStvr zLU)7UOJ*jZu8Z$%N0x*;i-tjGc?aTgHWj^#om|60t>%%5yo%n}zU-9IuWC5oGvke2Oc z?=HAxX<;?D-fr#dVsW>njv4MEAx=$kTk>pF+50m%pNjcPcdN^?+!?3W;AC@F#!&$? zs;M$tds}kv7m%$bG*u3s=6lKoEnDV2)E=I}-nwlB3pt_({0ArC74WhvwGqheDA~&` zXklNjp&5W!;*5B&%kM0}x|AVom%oghVsIpSBtVC7l=7D~=;#VP4+#T#7Jzdruhah0 zoD4gJ(DpWjH}MjNX0&QATTg}6MaA4Wbf&FS{YMF@n@8Qd9W{E?zm|iz>Yc?Ppk;(o zaH)GML9~?V)GqfUwGkMuODQ9q?2AYFM-`&H**Rzy7d2b;v8ao-!&+;70D9CS96G+z zQW1A6zwy(?elIp;_s}MXoz*)MKY$5!l!Por9ps2?DB1y8nODv`?B@+FuSH`eP~%Fx zKO?_V(mtL>9l=%Iq-QyQ5IpTGPt=j=ePVyF$gcw>`TBuM4Teg=;+- zzhIm$d-g#$jzTWgMN7l2E~>D5OFu)uW#6x8Xgd0tb_$!4{l39dUdHUgVqV&Bv6t2% zi$itqjCExkw{Lw5oJ*Yt6^%dYQsPfJl+I7fp##BZfVbBH+W+jOM|u9D)EWNxi-=0v z87IjICVx>i+MkbBUB0Uw?A!9GeXRU>jllxGWG-V0wB?v~Lj{JSBs8OvszIOylaFMw zXI+s*vl~qm$A{p{9cVN(mbF#-SUeeL*Ez@s9(vfNdQxTI)u?se9xi|UDNNsfdFXGl z@3%y|Zy0ssglwsk)yNNBN+bptPXolJ?8^AGD99z6g34u#4O;f;r2)JV_SYEU*1-sS3+68@Q+JL}6by{Eq!is64XC zv3Zh}{&r@K>Ixn7WPHEI2UmHv|K5>OEpkh4dyeK-G(GoNL_hN=xOMtii_WHbIz!#2 z?sK>B3<`MClqu%Hv^hm9C7e$n>f}LO| zAOa{&t-W-N1$%(+O61yT+&+hzaoF^9Z}6Rl_8cX^7%^*EoQ9ig50WAZ)7j9xzHEBl z(9G}g$7#k5fe(Tq$(BBd2v%jS%b#saJK`jnEN^J;Nml|gyz?kq3DPr8+e`3yU*blLmH?byXW9{MDyEUH+mx@;PLXnbJ(=W$GoKes2T0zK>HA19 z$&~WikSwdul4nHh=OlG?FKrFlv&ijH{z`Fpzwq(P0(vyyQk=OSrDy?_s7HwwN60Dj znOVu3-q@(+Q3Nj^YPI=@(PhAFJqgYUHrP5itNf~R7b4nNBnAJ57%t%glKqK*%lEt0 z&h{tnFQHBTK1?c*4(mH&YYGIvL@(Sp9$+tU`DYjB+b>}K)PZ1MSUm^Ed-bi=x@Bdl zORK&1O&_^34iu)imCxm&`{^dbZE2N#0h>4ys<9bg*k^wMBvuX`p+8(&6!1S}*@wFk zN&DL*3x2@1lnDL^&BFxAzSCE| z*pwP%UtIzzTnUOu^0GB8p~z}^*%*5I>a9pklrw%UI_tPpZz+t$x{f4ul$W_PY@x{Q z$`@{bwk7R|@Arr`Q{8x3;yZgK?Z`DfQYV#LK2SQBtM2kEmS2?(|^V&UI#87#(3?BWtQr@asBsfz=vftyGCF;SPO7>J|9O9ipYJ<9Je?4(HU zjFTSaB%D3ihJeSObHGz}s<0y|bMPc8gw6-=#660TADvZYe{Mor;M#~ug05wg-pcV$ zw>Xu^x2U7`*lf)n7da7PZI|Rl+Ryg;vRla^n21>x;@)4ho5YPdZ73W>TH#ve?qG8* zi{Ig2V{s^BcVHWd{Mmse+JQ=TCwX9&)hD<(7GlJt{U2q}L_-3gToEun+*QcXHG6gg z#wUC2Iq&1Nh~;x?hVZyO%D=QzQ~-+Aoy+q|2kdm~X(Uaj z_3fX%fjnLMb0H|XgEZYmnhvL9wWAV`IpyHs0p0r{7z-u04_;2qW3WM8mE&S)%p*_BaOh}WQ4Ca1AXPQ!ERtT(8}De*qI89?Ci)fM3nr{WER z1Q}N2j*lCq)6{X7@;Ua`fYt$P$R4c!4cm*p>?5rb4yQf5)t0T>{)D+cJUzjJ`=YYY zrWpOb@+Fd|f9(p|4W1?$s}B`xOVSakpIVFp0QPJrf?Y+tx|qsSeS}gt?5n`jBKsf0 z84-r)anynn1=RAgwnDEl&x`$PBNbEk)FQKH(c_gAzc_(<@InLPG_gIYxPMP0vFfWt z=%TOeXGFyNBUk-&?kG`i8*l8KmPZZw_f#X7HkUWLu|b=uM;f)8uH&(Z=tV19!x&>} zJYBRIH?qd%J-t7nJM~j-Sa$d;t+k2Nb1&Dghf*kCPklz*YNnP>omFt{5-Mtegi_ER zRhO#u=LtCIlRQ!WJt~iTe&4}_YuBB@U&oUswQc+tG!1}fx^j_UFLXisIDpz*>c&{@ z1Ee65s2$cLsiqmI(IH$Ih6;CUQE0lk<^a5|y!@V)32c4-_m=VO&bvP7IF7qI=mOiY zBguTrA}={bbmDy+PjYH(`{`X~(P+FSX~Ye- zYyY=+6L~>QPF`YX*w<4A#h#@isFW;zd1 z%|$Tqm-XAwL{6lmrl+TLkva&Pnz%I!;1+f-^wrR8H!oy+;M!I9q)SsXJn z5pNwf#u_hSf2Ps=ohBGd?$$Ao6aCpiE+UvO+}1flU5jiqPm?K)F0XGU$#V5iH`L#Y zKZxCgbUuCivPk3~@az!z_u#Mt2V_OYSU{4^M#$>TD2s*<I5HZ}sXmsR5&1&h)q(#4Aopq}kaGEb`Pf=e~%wwP^MpmO%)GWWd+!|BwpgLsj!l=hq zC$L_tHYM~2k`c2B7lZREWVIWFMEQFp*1W!Ijm5IJCh@+~sE!FRvs5gLy;{!LY$X)UyjvSHjlJgM?Jt& zeep3V59rqq(gSRkZ?olF*68b<@Zm7M!C!k(Nm{Px3stn=5g54Xq1%z-VSJCC93*x;?tTYg{-TT>E_4bdC@CB9vw zhnQ{K;Dkp-;ydjVw=?Iqg$aE`Vh&GiYqUpA?Y3SPtA8zTbK6?I&Bbfkh3(H-lGskA z=XE^>>upZ%)~?O2gIPeCobX)kEa2~D8OKigZ|G}571@U)2T3{Vss=Jsc;$a;HR(jXc9Eq#lAN+S zQ6IvST`&aw)g08Z7%1#vWy!{4Qeh_mi`lwIGNVvZTgG-jGRW%f%)WZ0uQjhTyZXrZ zP8I!i>8M@SvCKznp(L%3){Ps}4^@{>x}z}D{&v9EI;1EWf94$pkZQ#>t~yQvgZKP+ z$LuJ%IsmqD+*x*^yjR@WzEt11&p>#-FIlghO4L1Qy}0JDJ3ooGz1f{s&$x%!mWr9) zN4JDRiw?l7TnnLbW0EB)G!7%HgbL-@i=}-!2sv4 zCmvT8Qm~ry-H3;mCj~1xA47Z;@!oO{SP6(GpoH_kL38LotE3>N7!CeJyp8xg&VNTd z&a+Z5m-A-fTCjL6&b2-11c$gwcfb+Kz-<$YU&fi3QAL0`^ zKZE$b^inJs%YkeHpi+B^_(bBHI6sW|0mL_QejxD!i4So8dg2EW zU(NY$#A8)M0-TQ_elYnL!Ez2*34k6z0-XO1nODH}KmwfqiFoo|gL$0)j`*R(=W_lG z@i35(0O!9TemL=|oIgSQ2;vhtf0X!qSaV2#19b$9B3|PBZsJE1--3fCbbxs5Y>)uw zUm+el5hTF*=ZS}N010sZY2vZ3K?1DrGsENPJ1P`NuPL;!Tc61s*h-U#K+A^${j~)J zT0RsiPqW?6O!3YoWkVn(r|^WxcJXTHKH{rMyAcE|O(JDR5OCWUi%97y7@uZp3F+l) zX|*6=?s_@9O1y z3YskFctNKLdW)d>g09o4SK2CoCk1^`(6|u{TH#p7s4&2>{~R&rPs49%qA|pMY1q3!irk_+>KXlO$4I%T3m}q1X$$OyQ8!&5}EN z3fueqjlCr?-!qV4rMh`TgF8kv9B18Lm|#75oXvb;v{l-}HoP#!nz^Uu-4}LQuQU2? zD6*lbk@$~g_SY9|gA5YgWs=+^NAI+3abUg^<+MWUWMfj1R!+}ma` z8o^tn1gY|Cy@1i-F9co{hW`khI?6wMg!nOG=WJ+*@W!QjJ)&PyKY=GM)A1}$u)1y( z33(#nh%iOB2s~BqDgISi532;;B5<$Jj0Xi?K%zoeZP)1zfzv%8cv^fB;A>Bo?cUVDBa@&_K#v1-xL?@akI{%hf9 zQNX;CD3$(=pR}qWOxt>Lyqk0~46i5GN;Gz@c1ik-v%f{7bngYDSG)P>+CGpNg^B#5kK1(;_=>Pz^%HnO z7*2nrNA(nk;X`%YE0u&LOhf{GtS-m=}H__8uDl&zC2J%T!Y4~R#pAmTYXCVI}>IwS}ozx)k@b6xJuGi!J zuTTDcB^oIITJNl3ahgR%;U7!q*jMGXl5D6#E3BVr`cvBc&DsU|fFBf=A7+xXp z)-as@41N)fO!`I_fg$Su6$Qe-y7;=l!#|?2sMSMvRX0@H22ymhkUYT3{Oq5C0M;eT*RvrY>qAB0UkY zjAfQxy`rWH+%>iPH)&Ldf*j!_~%~B4g6(2zwx2V=Ztz@)$s`tI{iDx zCDvhAoTZk!WC_&#xT{Y@C;F_*;+3n{E?-kv(`j$z|L3cCF?F}I6?J)*RCc6pbewTE zFz7)RcK~MjKtf03l(2z04ov81oI$GD<^vPrj1!1K6WEahqvMQoj6t7Z9S@G~*n*=l zg%;*GI3dnB5*f6KDF;V)YwD-xF{m?|UA6aQO$QUYg@5vQt_5yiKbCSRHm)RDFJ;hs znd{K#ZiWwRd?Wexjl8bPLkZms@6av8+ezYeX@_FF8LsGp_DQJ`k!-}_&fN-xBU&Jm zb0c`Ad505X+P3e+Fww^vKyvYLAHR5d56`3 zblJtMN2XZ)yI3~J$lYuU$Pe$bI*=FkGV9SP)_dM#*+-}JSP{@GEf!wRAh1d7iKD}< VO~+W>(J3(_h11a#&0-Gr`#-&B)aL*I delta 18343 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 WwT{!4`d8W7#=C{;>+E1-zyAj~aFY%I delta 19688 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 d>=V1!H^03x Date: Sat, 9 May 2026 22:08:25 -0400 Subject: [PATCH 35/47] Build before test --- .github/workflows/test.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d93f2da..4584f39 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,4 +40,13 @@ jobs: - run: make lint - run: make ctest if: runner.os != 'Windows' + + - name: Clean stale binaries + shell: bash + run: rm -rf bin + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + - name: Build native module + run: cmake --build build --config Release + - run: make test \ No newline at end of file From cb8bb57fda4bff54676d76afbab7e36c3333b036 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 22:15:15 -0400 Subject: [PATCH 36/47] Set -D_POSIX_C_SOURCE=200809L for ctest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit — `Makefile:81` now passes `-D_POSIX_C_SOURCE=200809L`, which exposes `strdup`'s declaration in `` so the return value is correctly typed as `char *`. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 77f7ddf..9804704 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ emacs-asan: .PHONY: ctest ctest: mkdir -p $(BUILD_DIR) - $(CC) -std=c11 -Wall -Wextra -O2 -I. -pthread \ + $(CC) -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -O2 -I. -pthread \ -o $(BUILD_DIR)/fzf-native-ctest fzf-native-ctest.c fzf.c $(BUILD_DIR)/fzf-native-ctest From fb43f7bc7cc4cbded2ede7cc7b6a31dc35fe4e5b Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 22:22:01 -0400 Subject: [PATCH 37/47] Use gnu11 instead --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9804704..6819377 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ emacs-asan: .PHONY: ctest ctest: mkdir -p $(BUILD_DIR) - $(CC) -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -O2 -I. -pthread \ + $(CC) -std=gnu11 -Wall -Wextra -O2 -I. -pthread \ -o $(BUILD_DIR)/fzf-native-ctest fzf-native-ctest.c fzf.c $(BUILD_DIR)/fzf-native-ctest From 4d564f1dc5346fb193b83fe3a46524908ff99f0a Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sat, 9 May 2026 23:27:24 -0400 Subject: [PATCH 38/47] Handle case mode and expose custom variable --- fzf-native-ctest.c | 58 ++++++++++++++++++++++----------------------- fzf-native-module.c | 57 +++++++++++++++++++++++++++++++------------- fzf-native-test.el | 21 ++++++++++++++++ fzf-native.el | 13 ++++++++++ 4 files changed, 104 insertions(+), 45 deletions(-) diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index 1bd9e86..0e38eb3 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -378,7 +378,7 @@ static void test_cache_insert_then_lookup_hit(void) { cache_init(&c, 20); ScoredStr top[2] = { make_top("alpha", 42), make_top("beta", 17) }; - cache_insert(&c, "fo", 1000, top, 2, NULL, 0); + cache_insert(&c, "fo", 1000, CaseSmart, top, 2, NULL, 0); ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; @@ -400,7 +400,7 @@ static void test_cache_lookup_miss_distinct_query(void) { Cache c; cache_init(&c, 20); ScoredStr top[1] = { make_top("alpha", 42) }; - cache_insert(&c, "fo", 100, top, 1, NULL, 0); + cache_insert(&c, "fo", 100, CaseSmart, top, 1, NULL, 0); ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; @@ -419,8 +419,8 @@ static void test_cache_insert_updates_in_place(void) { ScoredStr v1[1] = { make_top("alpha", 10) }; ScoredStr v2[2] = { make_top("alpha", 99), make_top("beta", 50) }; - cache_insert(&c, "fo", 100, v1, 1, NULL, 0); - cache_insert(&c, "fo", 200, v2, 2, NULL, 0); + cache_insert(&c, "fo", 100, CaseSmart, v1, 1, NULL, 0); + cache_insert(&c, "fo", 200, CaseSmart, v2, 2, NULL, 0); CHECK(c.count == 1); ScoredStr *out = NULL; @@ -446,12 +446,12 @@ static void test_cache_lru_eviction_at_capacity(void) { char qbuf[16]; for (size_t i = 0; i < MAX; i++) { snprintf(qbuf, sizeof qbuf, "q%zu", i); - cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); + cache_insert(&c, qbuf, (size_t)i, CaseSmart, one, 1, NULL, 0); } CHECK(c.count == MAX); /* Insert one more — should evict q0 (the LRU tail). */ - cache_insert(&c, "extra", 999, one, 1, NULL, 0); + cache_insert(&c, "extra", 999, CaseSmart, one, 1, NULL, 0); CHECK(c.count == MAX); ScoredStr *out = NULL; @@ -489,7 +489,7 @@ static void test_cache_touch_on_hit(void) { char qbuf[16]; for (size_t i = 0; i < MAX; i++) { snprintf(qbuf, sizeof qbuf, "q%zu", i); - cache_insert(&c, qbuf, (size_t)i, one, 1, NULL, 0); + cache_insert(&c, qbuf, (size_t)i, CaseSmart, one, 1, NULL, 0); } /* Touch q0 — moves it to head (MRU). */ @@ -500,7 +500,7 @@ static void test_cache_touch_on_hit(void) { free(out); /* Now the LRU tail is q1. Insert one more; q1 should be evicted. */ - cache_insert(&c, "extra", 999, one, 1, NULL, 0); + cache_insert(&c, "extra", 999, CaseSmart, one, 1, NULL, 0); out = NULL; out_sidx = NULL; out_count = 0; out_gen = 0; CHECK(cache_lookup_exact(&c, "q0", &out, &out_count, &out_sidx, &out_gen) == true); @@ -517,7 +517,7 @@ static void test_cache_insert_zero_count(void) { stores and looks up cleanly. */ Cache c; cache_init(&c, 20); - cache_insert(&c, "nothing", 500, NULL, 0, NULL, 0); + cache_insert(&c, "nothing", 500, CaseSmart, NULL, 0, NULL, 0); ScoredStr *out = (ScoredStr *)0xdeadbeef; SharedIdx *out_sidx = NULL; @@ -536,7 +536,7 @@ static void test_cache_pool_gen_distinguishes_stale(void) { Cache c; cache_init(&c, 20); ScoredStr top[1] = { make_top("alpha", 1) }; - cache_insert(&c, "fo", 100, top, 1, NULL, 0); + cache_insert(&c, "fo", 100, CaseSmart, top, 1, NULL, 0); ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; @@ -546,7 +546,7 @@ static void test_cache_pool_gen_distinguishes_stale(void) { free(out); /* Re-insert at a new pool_gen; lookup should reflect the latest. */ - cache_insert(&c, "fo", 5000, top, 1, NULL, 0); + cache_insert(&c, "fo", 5000, CaseSmart, top, 1, NULL, 0); out = NULL; out_count = 0; out_gen = 0; CHECK(cache_lookup_exact(&c, "fo", &out, &out_count, &out_sidx, &out_gen) == true); CHECK(out_gen == 5000); @@ -569,8 +569,8 @@ static void test_subsumes_pattern_extending_term_via_byte_prefix(void) { static void test_subsumes_pattern_adding_term_at_end(void) { /* "fo" → "fo bar": both rules agree. Verify term-set path. */ - fzf_pattern_t *p1 = parse_query_for_cache("fo"); - fzf_pattern_t *p2 = parse_query_for_cache("fo bar"); + fzf_pattern_t *p1 = parse_query_for_cache("fo", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("fo bar", CaseSmart); CHECK(p1 && p2); CHECK(subsumes_pattern(p1, p2) == true); CHECK(subsumes_pattern(p2, p1) == false); @@ -581,8 +581,8 @@ static void test_subsumes_pattern_adding_term_at_end(void) { static void test_subsumes_pattern_adding_term_at_start(void) { /* "fo" → "x fo": v2-only case. Byte-prefix says NO (fo not prefix of x fo), term-set says YES (fo's terms ⊆ x fo's terms). */ - fzf_pattern_t *p1 = parse_query_for_cache("fo"); - fzf_pattern_t *p2 = parse_query_for_cache("x fo"); + fzf_pattern_t *p1 = parse_query_for_cache("fo", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("x fo", CaseSmart); CHECK(p1 && p2); CHECK(subsumes("fo", "x fo") == false); /* v1 misses */ CHECK(subsumes_pattern(p1, p2) == true); /* v2 catches */ @@ -595,8 +595,8 @@ static void test_subsumes_pattern_term_reorder(void) { /* "foo bar" and "bar foo" are semantically equivalent in fzf — same term set, different textual order. Term-set rule sees mutual subsumption; byte-prefix rule sees neither. */ - fzf_pattern_t *p1 = parse_query_for_cache("foo bar"); - fzf_pattern_t *p2 = parse_query_for_cache("bar foo"); + fzf_pattern_t *p1 = parse_query_for_cache("foo bar", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("bar foo", CaseSmart); CHECK(p1 && p2); CHECK(subsumes("foo bar", "bar foo") == false); CHECK(subsumes("bar foo", "foo bar") == false); @@ -609,8 +609,8 @@ static void test_subsumes_pattern_term_reorder(void) { static void test_subsumes_pattern_negation_at_start(void) { /* "fo" → "!x fo": adding a negation term in non-prefix position. Term-set rule catches it; byte-prefix doesn't. */ - fzf_pattern_t *p1 = parse_query_for_cache("fo"); - fzf_pattern_t *p2 = parse_query_for_cache("!x fo"); + fzf_pattern_t *p1 = parse_query_for_cache("fo", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("!x fo", CaseSmart); CHECK(p1 && p2); CHECK(subsumes_pattern(p1, p2) == true); fzf_free_pattern(p1); @@ -621,8 +621,8 @@ static void test_subsumes_pattern_or_query_rejected(void) { /* "fo | bar" parses as ONE term-set with TWO terms (within a set = OR; across sets = AND). subsumes_pattern rejects any term-set with >1 term — it can never serve as a refinement source. */ - fzf_pattern_t *p1 = parse_query_for_cache("fo"); - fzf_pattern_t *p2 = parse_query_for_cache("fo | bar"); + fzf_pattern_t *p1 = parse_query_for_cache("fo", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("fo | bar", CaseSmart); CHECK(p1 && p2); CHECK(p1->size == 1 && p1->ptr[0]->size == 1); /* "fo": 1 set, 1 term */ CHECK(p2->size == 1 && p2->ptr[0]->size == 2); /* "fo|bar": 1 set, 2 terms */ @@ -634,8 +634,8 @@ static void test_subsumes_pattern_or_query_rejected(void) { static void test_subsumes_pattern_distinct_terms(void) { /* "foo" and "bar" share no terms; neither subsumes the other. */ - fzf_pattern_t *p1 = parse_query_for_cache("foo"); - fzf_pattern_t *p2 = parse_query_for_cache("bar"); + fzf_pattern_t *p1 = parse_query_for_cache("foo", CaseSmart); + fzf_pattern_t *p2 = parse_query_for_cache("bar", CaseSmart); CHECK(p1 && p2); CHECK(subsumes_pattern(p1, p2) == false); CHECK(subsumes_pattern(p2, p1) == false); @@ -648,7 +648,7 @@ static void test_subsumes_pattern_distinct_terms(void) { the lookup logic without caring about the actual indices. */ static void cache_insert_eligible(Cache *c, const char *query, size_t pool_gen) { uint32_t idx[1] = { 0 }; - cache_insert(c, query, pool_gen, NULL, 0, idx, 1); + cache_insert(c, query, pool_gen, CaseSmart, NULL, 0, idx, 1); } static void test_cache_lookup_prefix_v2_finds_term_subset(void) { @@ -660,7 +660,7 @@ static void test_cache_lookup_prefix_v2_finds_term_subset(void) { ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "x fo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(cache_lookup_prefix(&c, "x fo", CaseSmart, &out, &out_count, &out_sidx, &out_gen) == true); CHECK(out_gen == 100); shared_idx_release(out_sidx); free(out); @@ -679,7 +679,7 @@ static void test_cache_lookup_prefix_v2_finds_reordered(void) { ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "bar foo", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(cache_lookup_prefix(&c, "bar foo", CaseSmart, &out, &out_count, &out_sidx, &out_gen) == true); CHECK(out_gen == 100); shared_idx_release(out_sidx); free(out); @@ -698,7 +698,7 @@ static void test_cache_lookup_prefix_picks_most_terms(void) { ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "fo bar baz", &out, &out_count, &out_sidx, &out_gen) == true); + CHECK(cache_lookup_prefix(&c, "fo bar baz", CaseSmart, &out, &out_count, &out_sidx, &out_gen) == true); CHECK(out_gen == 200); /* "fo bar" entry wins */ shared_idx_release(out_sidx); free(out); @@ -715,7 +715,7 @@ static void test_cache_lookup_prefix_skips_or_in_query(void) { ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "fo | bar", &out, &out_count, &out_sidx, &out_gen) == false); + CHECK(cache_lookup_prefix(&c, "fo | bar", CaseSmart, &out, &out_count, &out_sidx, &out_gen) == false); cache_free(&c); } @@ -729,7 +729,7 @@ static void test_cache_lookup_prefix_skips_exact_match(void) { ScoredStr *out = NULL; SharedIdx *out_sidx = NULL; size_t out_count = 0, out_gen = 0; - CHECK(cache_lookup_prefix(&c, "fo bar", &out, &out_count, &out_sidx, &out_gen) == false); + CHECK(cache_lookup_prefix(&c, "fo bar", CaseSmart, &out, &out_count, &out_sidx, &out_gen) == false); cache_free(&c); } diff --git a/fzf-native-module.c b/fzf-native-module.c index c7e9cee..2b70ed1 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -340,6 +340,21 @@ static void apply_highlight_positions(emacs_env *env, fzf_free_positions(pos); } +/* Read `fzf-native-case-mode' via symbol-value and resolve to fzf_case_types. + Recognized symbol values: smart (default), ignore, respect. + Falls back to CaseSmart on any read or comparison failure. */ +static fzf_case_types resolve_fzf_native_case_mode(emacs_env *env) { + emacs_value sym = env->intern(env, "fzf-native-case-mode"); + emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + return CaseSmart; + } + if (env->eq(env, v, env->intern(env, "ignore"))) return CaseIgnore; + if (env->eq(env, v, env->intern(env, "respect"))) return CaseRespect; + return CaseSmart; +} + /* Read fussy-fzf-native-highlight via symbol-value and resolve to a cap. Returns: 0 — no highlighting (nil, negative, unreadable, or zero). @@ -430,7 +445,8 @@ emacs_value fzf_native_score_all(emacs_env *env, return Qnil; } - fzf_pattern_t *pattern = fzf_parse_pattern(CaseIgnore, false, query.b, true); + fzf_case_types case_mode = resolve_fzf_native_case_mode(env); + fzf_pattern_t *pattern = fzf_parse_pattern(case_mode, false, query.b, true); struct Shared shared = { .pattern = pattern, .batches = batches, @@ -492,12 +508,12 @@ emacs_value fzf_native_score_all(emacs_env *env, /* Resolve C-side highlight cap from fussy-fzf-native-highlight. After the sort, xs[0] is the highest-scoring candidate, so the top-N candidates are xs[0..hl_cap-1]. The original parsing pattern was already freed; - re-parse for highlighting (case-insensitive matches the async path). */ + re-parse for highlighting using the same case mode the scoring used. */ size_t hl_cap = resolve_fussy_highlight_cap(env, len); fzf_pattern_t *hl_pattern = NULL; fzf_slab_t *hl_slab = NULL; if (hl_cap > 0) { - hl_pattern = fzf_parse_pattern(CaseIgnore, false, query.b, true); + hl_pattern = fzf_parse_pattern(case_mode, false, query.b, true); if (hl_pattern) hl_slab = fzf_make_default_slab(); if (!hl_slab) { if (hl_pattern) { fzf_free_pattern(hl_pattern); hl_pattern = NULL; } @@ -609,7 +625,8 @@ emacs_value fzf_native_score(emacs_env *env, ptrdiff_t nargs, emacs_value args[] * pattern char* : Pattern you want to match. e.g. "src | lua !.c$ * fuzzy bool : Enable or disable fuzzy matching */ - fzf_pattern_t *pattern = fzf_parse_pattern(CaseSmart, false, query.b, true); + fzf_case_types case_mode = resolve_fzf_native_case_mode(env); + fzf_pattern_t *pattern = fzf_parse_pattern(case_mode, false, query.b, true); if (!pattern) { goto err; } fzf_slab_t *slab; @@ -898,11 +915,12 @@ static bool subsumes_pattern(const fzf_pattern_t *p_prime, /* Parse a query string into an fzf_pattern_t. Returns NULL if the query is empty or parsing fails. fzf_parse_pattern mutates its input, so we strdup first and free after — the returned pattern is self-contained. */ -static fzf_pattern_t *parse_query_for_cache(const char *query) { +static fzf_pattern_t *parse_query_for_cache(const char *query, + fzf_case_types case_mode) { if (!query || !*query) return NULL; char *dup = strdup(query); if (!dup) return NULL; - fzf_pattern_t *p = fzf_parse_pattern(CaseIgnore, false, dup, true); + fzf_pattern_t *p = fzf_parse_pattern(case_mode, false, dup, true); free(dup); return p; } @@ -951,11 +969,12 @@ static bool cache_lookup_exact(Cache *c, const char *query, back to byte-prefix-length tiebreak when both have equal term counts (or for entries whose parsed pattern is unavailable). */ static bool cache_lookup_prefix(Cache *c, const char *query, + fzf_case_types case_mode, ScoredStr **out_top, size_t *out_top_count, SharedIdx **out_m_idx, size_t *out_pool_gen) { if (strchr(query, '|')) return false; /* fast reject */ - fzf_pattern_t *p_query = parse_query_for_cache(query); + fzf_pattern_t *p_query = parse_query_for_cache(query, case_mode); /* If parse failed and query isn't empty, fall back to byte-prefix only. Empty query has p_query == NULL but byte-prefix subsumes("", anything) also returns true so the loop still works. */ @@ -1012,6 +1031,7 @@ static bool cache_lookup_prefix(Cache *c, const char *query, or empty match sets); the entry is still inserted, but is then ineligible as a prefix-refinement source. */ static void cache_insert(Cache *c, const char *query, size_t pool_gen, + fzf_case_types case_mode, const ScoredStr *top, size_t top_count, const uint32_t *m_idx_src, size_t m_idx_count) { /* Pre-allocate everything outside the mutex. */ @@ -1027,7 +1047,7 @@ static void cache_insert(Cache *c, const char *query, size_t pool_gen, /* Parse once on insert so cache_lookup_prefix doesn't pay parse cost on every iteration of its scan loop. NULL is fine — entries with NULL parsed only participate via the byte-prefix subsumption fallback. */ - fzf_pattern_t *parsed = parse_query_for_cache(query); + fzf_pattern_t *parsed = parse_query_for_cache(query, case_mode); if (!q_dup) { free(top_dup); @@ -1109,6 +1129,7 @@ typedef struct { pthread_cond_t score_req_cond; char *score_req_filter; /* owned; NULL = nothing pending */ size_t score_req_limit; + fzf_case_types score_req_case_mode; /* Refinement request: when score_req_refine_idx is non-NULL the next scoring run scores only those candidate indices plus s->cands[refine_delta_from..count]. Ownership transfers to the scoring thread along with score_req_filter. */ @@ -1535,10 +1556,11 @@ static void *scoring_thread_fn(void *arg) { pthread_mutex_unlock(&s->score_req_mu); break; } - char *filter = s->score_req_filter; /* steal ownership */ - size_t limit = s->score_req_limit; - SharedIdx *refine_idx = s->score_req_refine_idx; /* steal */ - size_t refine_delta_from = s->score_req_refine_delta_from; + char *filter = s->score_req_filter; /* steal ownership */ + size_t limit = s->score_req_limit; + fzf_case_types case_mode = s->score_req_case_mode; + SharedIdx *refine_idx = s->score_req_refine_idx; /* steal */ + size_t refine_delta_from = s->score_req_refine_delta_from; s->score_req_filter = NULL; s->score_req_refine_idx = NULL; /* Record what we're about to score so main thread can skip abort for same filter */ @@ -1634,7 +1656,7 @@ static void *scoring_thread_fn(void *arg) { unsigned max_workers = (unsigned)sysconf(_SC_NPROCESSORS_ONLN); size_t flen = strlen(filter); - fzf_pattern_t *pattern = flen ? fzf_parse_pattern(CaseIgnore, false, filter, true) : NULL; + fzf_pattern_t *pattern = flen ? fzf_parse_pattern(case_mode, false, filter, true) : NULL; bool has_pattern = (pattern != NULL); struct AsyncScoringShared shared = { @@ -1690,7 +1712,7 @@ static void *scoring_thread_fn(void *arg) { /* Cache the result. pool_gen = count (the pool size we actually scored). For refine runs, count may be > refine_delta_from, so the new entry supersedes the old one as a refinement source for the same query. */ - cache_insert(&s->cache, filter, count, flat, emit, m_idx_buf, pos); + cache_insert(&s->cache, filter, count, case_mode, flat, emit, m_idx_buf, pos); free(m_idx_buf); /* Clear active-filter marker before publishing results */ @@ -1741,6 +1763,8 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, if (nargs > 2 && !env->eq(env, args[2], Qnil)) limit = (size_t)env->extract_integer(env, args[2]); + fzf_case_types case_mode = resolve_fzf_native_case_mode(env); + /* Cache lookup. Exact-fresh hits skip scoring entirely; exact-stale and prefix hits dispatch a refinement scoring run that scans only the prior match set + candidates that arrived since. Misses fall @@ -1755,7 +1779,7 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, &cached_m_idx, &cached_pool_gen); bool prefix_hit = false; if (!exact_hit) - prefix_hit = cache_lookup_prefix(&s->cache, filter, + prefix_hit = cache_lookup_prefix(&s->cache, filter, case_mode, &cached_top, &cached_count, &cached_m_idx, &cached_pool_gen); @@ -1793,6 +1817,7 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, shared_idx_release(s->score_req_refine_idx); s->score_req_filter = filter; /* transfer */ s->score_req_limit = limit; + s->score_req_case_mode = case_mode; s->score_req_refine_idx = cached_m_idx; /* transfer (NULL on miss) */ s->score_req_refine_delta_from = cached_pool_gen; pthread_cond_signal(&s->score_req_cond); @@ -1842,7 +1867,7 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, /* nil or negative integer → hl_cap stays 0, no highlighting */ } if (hl_cap > 0) { - hl_pattern = fzf_parse_pattern(CaseIgnore, false, filter_for_hilit, true); + hl_pattern = fzf_parse_pattern(case_mode, false, filter_for_hilit, true); hl_slab = fzf_make_default_slab(); } diff --git a/fzf-native-test.el b/fzf-native-test.el index dcf1bc6..d667479 100644 --- a/fzf-native-test.el +++ b/fzf-native-test.el @@ -76,6 +76,27 @@ (result (fzf-native-score str "d"))) (should (equal result '(16))))) +(ert-deftest fzf-native-score-case-mode-smart-test () + "Default `fzf-native-case-mode' is smart: lowercase query is +case-insensitive, query with any uppercase becomes case-sensitive." + (should (eq fzf-native-case-mode 'smart)) + ;; Lowercase query → insensitive: matches uppercase target. + (should (equal (fzf-native-score "Foo" "foo") '(80))) + ;; Uppercase query → sensitive: lowercase target no longer matches. + (should (equal (fzf-native-score "foo" "Foo") '(0)))) + +(ert-deftest fzf-native-score-case-mode-ignore-test () + "`fzf-native-case-mode' = ignore matches regardless of case." + (let ((fzf-native-case-mode 'ignore)) + (should (equal (fzf-native-score "foo" "Foo") '(80))) + (should (equal (fzf-native-score "Foo" "foo") '(80))))) + +(ert-deftest fzf-native-score-case-mode-respect-test () + "`fzf-native-case-mode' = respect requires exact case." + (let ((fzf-native-case-mode 'respect)) + (should (equal (fzf-native-score "Foo" "foo") '(0))) + (should (equal (fzf-native-score "foo" "foo") '(80))))) + (ert-deftest fzf-native-score-with-default-slab-benchmark-test () "Test scoring with slab is faster." (let* ((slab (fzf-native-make-default-slab)) diff --git a/fzf-native.el b/fzf-native.el index 380af1b..9482fcc 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -54,6 +54,19 @@ confirmation before compiling." :type 'boolean :group 'fzf-native) +(defcustom fzf-native-case-mode 'smart + "How fzf-native treats letter case when matching queries. +smart Case-insensitive when the query is all lowercase; case-sensitive + once it contains any uppercase character (fzf's default). +ignore Always case-insensitive. +respect Always case-sensitive. + +Read on every scoring call; changes take effect immediately." + :type '(choice (const :tag "Smart case (default)" smart) + (const :tag "Ignore case" ignore) + (const :tag "Respect case" respect)) + :group 'fzf-native) + (defun fzf-native-module--cmake-is-available () "Return t if cmake is available. CMake is needed to build fzf-native, here we check that we can find From 0601dd74a2b57b17d5f75139fec943a9706523a7 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sun, 10 May 2026 10:17:14 -0400 Subject: [PATCH 39/47] Wipe text properties before adding them --- fzf-native-module.c | 150 +++++++++++++++++++++++++++++++++++++++++++- fzf-native.el | 1 + 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 2b70ed1..10bbdac 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -93,6 +93,7 @@ emacs_value Ffunctionp, Fsymbolp, Fsymbolname, Flength, Fnth, Fprinc, Freverse; emacs_value Qcompletion_score, Fput_text_property, Qzero, Qone; emacs_value Fencode_coding_string, Qutf_8; emacs_value Qface, Qcompletions_common_part; +emacs_value Fremove_text_properties, Qface_nil_plist; /** An Emacs string made accessible by copying. */ @@ -309,6 +310,23 @@ static void apply_highlight_positions(emacs_env *env, fzf_pattern_t *pattern, fzf_slab_t *slab, emacs_value str_val) { + /* Empty candidates can't carry text properties or matched positions; skip + the funcalls and the get_positions slab work entirely. */ + if (cstr[0] == '\0') return; + /* Strip any `completions-common-part' face left over from a prior highlight + pass on the same Emacs string (fussy reuses caller-owned candidate strings + across keystrokes; without this, e.g. "ab" highlight [0,2] persists when + the user backspaces to "a" because put-text-property only writes the new + [0,1] range and leaves byte [1,2] highlighted). One funcall per + highlighted candidate; the (face nil) plist is interned at init. */ + emacs_value len_v = env->funcall(env, Flength, 1, &str_val); + if (env->non_local_exit_check(env) == emacs_funcall_exit_return) { + emacs_value rargs[4] = { Qzero, len_v, Qface_nil_plist, str_val }; + env->funcall(env, Fremove_text_properties, 4, rargs); + env->non_local_exit_clear(env); + } else { + env->non_local_exit_clear(env); + } fzf_position_t *pos = fzf_get_positions(cstr, pattern, slab); if (pos && pos->size > 0) { /* pos->data[] is in descending order: pos->data[0] = highest position. @@ -378,6 +396,12 @@ static size_t resolve_fussy_highlight_cap(emacs_env *env, size_t len) { return (size_t)n > len ? len : (size_t)n; } +// Forward declare. +emacs_value fzf_native_highlight_all(emacs_env *env, + ptrdiff_t nargs, + emacs_value args[], + void *data_ptr); + // fzf-native-score-all COLLECTION QUERY &optional SLAB emacs_value fzf_native_score_all(emacs_env *env, ptrdiff_t nargs, @@ -394,9 +418,13 @@ emacs_value fzf_native_score_all(emacs_env *env, fzf_log("fzf_native_score_all START: query='%.*s'\n", (int)query.len, query.b); - // Return all candidates if query is empty with doing anything else. + /* Empty query: don't score, but still strip stale `completions-common-part' + face from the top-N candidates so backspacing to "" clears highlights left + behind by a prior query. Delegate to highlight-all, which respects + `fussy-fzf-native-highlight' for the cap. */ if (query.len == 0) { - result = args[0]; + emacs_value hargs[2] = { args[0], args[1] }; + result = fzf_native_highlight_all(env, 2, hargs, NULL); success = true; goto err; } @@ -561,6 +589,104 @@ emacs_value fzf_native_score_all(emacs_env *env, return result; } +/* Strip `completions-common-part' face from STR_VAL without applying any new + positions. Used by the empty-query path of `fzf-native-highlight-all', and + shares the (face nil) plist with `apply_highlight_positions'. */ +static void clear_highlight_face(emacs_env *env, emacs_value str_val) { + emacs_value len_v = env->funcall(env, Flength, 1, &str_val); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + return; + } + emacs_value rargs[4] = { Qzero, len_v, Qface_nil_plist, str_val }; + env->funcall(env, Fremove_text_properties, 4, rargs); + env->non_local_exit_clear(env); +} + +// fzf-native-highlight-all COLLECTION QUERY +// +// Apply `completions-common-part' face to each candidate in COLLECTION +// against QUERY without scoring or sorting. Intended for callers that +// already have a sorted result set but need to refresh stale highlights +// (e.g. fussy cache hits or the empty-query branch, where the C scoring +// path is skipped entirely and previously-applied face properties from +// a different query persist on the same Emacs string objects). +// +// When QUERY is empty, performs a clear-only pass: removes the face +// from the top-N candidates without computing new positions. This is +// the path that fixes the "type m, backspace, highlight stays" case. +// +// Honors `fussy-fzf-native-highlight' the same way `fzf-native-score-all' +// does: nil → no-op, t → process all, N → process top N. COLLECTION +// is assumed to be in display order (highest-scoring first). +// +// Returns COLLECTION unchanged. Mutates the candidate strings in-place. +emacs_value fzf_native_highlight_all(emacs_env *env, + ptrdiff_t UNUSED(nargs), + emacs_value args[], + void UNUSED(*data_ptr)) { + struct Bump *bump = NULL; + fzf_pattern_t *pattern = NULL; + fzf_slab_t *slab = NULL; + + /* Treat an empty *or* undecodable query as clear-only. The stale face + properties live on the COLLECTION strings, not on the query, so we still + need to walk the collection and strip face even if the query couldn't be + coerced through `encode-coding-string'. */ + struct Str query = copy_emacs_string(env, &bump, args[1]); + bool clear_only = (!query.b || query.len == 0); + + /* Accept both lists and vectors; mirror score-all's normalization so + callers don't have to care which one they have on hand. */ + emacs_value collection = args[0]; + if (!env->eq(env, env->type_of(env, collection), env->intern(env, "vector"))) { + collection = env->funcall(env, Fvconcat, 1, (emacs_value[]) { args[0] }); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + goto done; + } + } + + ptrdiff_t n = env->vec_size(env, collection); + if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { + env->non_local_exit_clear(env); + goto done; + } + + /* Cap the highlight/clear pass to the user's `fussy-fzf-native-highlight' + setting. Returns 0 when highlighting is disabled — at that point the + candidates can't have stale face from this module either, so skip. */ + size_t hl_cap = resolve_fussy_highlight_cap(env, (size_t)n); + if (hl_cap == 0) goto done; + + if (!clear_only) { + fzf_case_types case_mode = resolve_fzf_native_case_mode(env); + pattern = fzf_parse_pattern(case_mode, false, query.b, true); + if (!pattern) goto done; + slab = fzf_make_default_slab(); + if (!slab) goto done; + } + + for (ptrdiff_t i = 0; i < (ptrdiff_t)hl_cap; i++) { + emacs_value value = env->vec_get(env, collection, i); + if (clear_only) { + clear_highlight_face(env, value); + } else { + struct Str s = copy_emacs_string(env, &bump, value); + if (!s.b) continue; + apply_highlight_positions(env, s.b, pattern, slab, value); + } + } + +done: + if (slab) fzf_free_slab(slab); + if (pattern) fzf_free_pattern(pattern); + bump_free(bump); + /* Always return the original COLLECTION (not the vector-coerced copy) + so list callers see their list back. */ + return args[0]; +} + /* Signal `(wrong-type-argument stringp VALUE)' if VALUE is not a string. Returns true on failure (caller should return immediately). */ static bool signal_if_not_string(emacs_env *env, emacs_value value) { @@ -1992,6 +2118,20 @@ int emacs_module_init(struct emacs_runtime *rt) { &data), }); + // fzf-native-highlight-all COLLECTION QUERY + env->funcall(env, env->intern(env, "defalias"), 2, (emacs_value[]) { + env->intern(env, "fzf-native-highlight-all"), + env->make_function(env, 2, 2, fzf_native_highlight_all, + "Apply fzf match highlights to COLLECTION against QUERY.\n" + "Mutates each candidate string's text properties in place;\n" + "stale `completions-common-part' face from a prior query is\n" + "stripped before new positions are applied. No scoring or\n" + "sorting is performed.\n" + "\n" + "\\(fn COLLECTION QUERY)", + &data), + }); + // fzf-native-score STR QUERY &optional SLAB env->funcall(env, env->intern(env, "defalias"), 2, (emacs_value[]) { env->intern(env, "fzf-native-score"), @@ -2091,6 +2231,12 @@ int emacs_module_init(struct emacs_runtime *rt) { Fencode_coding_string = env->make_global_ref(env, env->intern(env, "encode-coding-string")); Qface = env->make_global_ref(env, env->intern(env, "face")); Qcompletions_common_part = env->make_global_ref(env, env->intern(env, "completions-common-part")); + Fremove_text_properties = env->make_global_ref(env, env->intern(env, "remove-text-properties")); + /* Pre-built (face nil) plist passed to remove-text-properties to strip the + `face' property regardless of value. Built once to avoid allocating a + fresh cons cell on every highlight call. */ + Qface_nil_plist = env->make_global_ref( + env, env->funcall(env, Flist, 2, (emacs_value[]){Qface, Qnil})); Qutf_8 = env->make_global_ref(env, env->intern(env, "utf-8")); Qlistofzero = env->make_global_ref( env, env->funcall(env, Fcons, 2, diff --git a/fzf-native.el b/fzf-native.el index 9482fcc..82e8887 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -21,6 +21,7 @@ :link '(url-link :tag "GitHub" "https://github.com/dangduc/fzf-native")) (declare-function fzf-native-score-all "fzf-native-module" (collection query &optional slab)) +(declare-function fzf-native-highlight-all "fzf-native-module" (collection query)) (declare-function fzf-native-score "fzf-native-module" (str query &optional slab)) (declare-function fzf-native-make-default-slab "fzf-native-module" ()) (declare-function fzf-native-make-slab "fzf-native-module" (size16 size32)) From 0c86c0d67abd90e543d2693c5c7de64c26ad7a1d Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sun, 10 May 2026 10:17:16 -0400 Subject: [PATCH 40/47] Restore chunked cands_top to bound reader allocations at 2 MB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flat s->cands pointer array (and its doubling realloc) was the last remaining O(N) allocation in the async path. At 33M candidates, growing to 67M slots requires a single 537 MB malloc plus a 264 MB transient memcpy. Under macOS memory-compressor pressure the kernel cannot find that much contiguous memory quickly, and every thread in the process stalls — Hang #2 in the project history. Replace the flat array with a two-level table: cands_top[] : CANDS_TOP_CAP slots (4096) × 8 B = 32 KB inline, zero-initialized at session start, never grown. cands_top[i] : CANDS_BLOCK_SIZE (256K) pointers × 8 B = 2 MB block, allocated lazily by the reader on first write. Index split: i = (hi << SHIFT) + lo hi = i >> 18 → which block lo = i & 0x3FFFF → which slot in that block Both single-cycle CPU instructions because BLOCK_SIZE is a power of 2. Largest single allocation the reader ever does: 2 MB regardless of pool size. macOS's compressor satisfies a 2 MB allocation in microseconds even under heavy pressure; a 537 MB allocation can stall for seconds. 1 G candidate ceiling (4096 × 256K = 2^32) is well past the practical limit of any realistic shell command. Reader changes (async_reader): - Compute (hi, lo) from s->count BEFORE arena_strdup. The reader is the sole writer to s->count, so reading without s->mu is safe. - Cap check first: if hi >= CANDS_TOP_CAP, drop the line entirely (don't even arena-allocate) and log verbosely with line preview — hitting 1 G candidates is so far outside expected behavior that it almost certainly indicates a broken upstream command (infinite loop, runaway find on a cyclic FS, etc.), and we want the cause obvious in the log. - Pre-allocate the new 2 MB block OUTSIDE s->mu. Doing the malloc under the lock would let a slow allocation stall the scoring thread's snapshot path — which is exactly the original Hang #2 problem. - Take s->mu briefly to publish the block pointer (if newly allocated) and write the slot + increment count. Scoring thread changes (scoring_thread_fn): - Full-pool snapshot walks block-by-block, doing a flat memcpy within each block. Boundary-crossing cost paid once per block (~250 times for a 60M pool — basically free) while inner loops match flat-array speed. - Refine path's matched_idx and delta loops resolve via the shift+mask accessor: snap[w] = s->cands_top[gi >> SHIFT][gi & MASK]. Random access pays one extra L1 cache line load on first access to each block, negligible vs string-comparison cost. Teardown (async_session_destroy): - Walk cands_top[0..CAP-1] and free each non-NULL block. Strings still freed in O(chunks) by arena_free. Init (fzf_native_async_start): - No initial allocation needed. cands_top is zero-initialized by the calloc that allocates the AsyncSession itself. ASYNC_INIT_CAP removed. --- fzf-native-ctest.c | 96 +++++++++++++++++++++++++++++++------ fzf-native-module.c | 113 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 174 insertions(+), 35 deletions(-) diff --git a/fzf-native-ctest.c b/fzf-native-ctest.c index 0e38eb3..6c1bb26 100644 --- a/fzf-native-ctest.c +++ b/fzf-native-ctest.c @@ -262,24 +262,33 @@ static void test_strip_ansi_bare_esc(void) { * async_reader (pipe-based; no Emacs runtime needed) * ===================================================================== */ +/* The `cap` parameter is ignored under the chunked-storage design — the + reader allocates blocks lazily. Kept in the signature so existing test + sites remain readable without rewrites. */ static AsyncSession *make_async_session(FILE *fp, size_t cap) { + (void)cap; AsyncSession *s = calloc(1, sizeof *s); if (!s) return NULL; - s->fp = fp; - s->cap = cap; - s->cands = calloc(cap, sizeof *s->cands); - if (!s->cands) { free(s); return NULL; } + s->fp = fp; pthread_mutex_init(&s->mu, NULL); return s; } static void free_async_session(AsyncSession *s) { arena_free(&s->arena); - free(s->cands); + for (size_t k = 0; k < CANDS_TOP_CAP; k++) + if (s->cands_top[k]) free(s->cands_top[k]); pthread_mutex_destroy(&s->mu); free(s); } +/* Convenience accessor: read s->cands_top[i >> SHIFT][i & MASK]. + Returns NULL if the block isn't allocated (which would be a bug). */ +static const char *cands_at(AsyncSession *s, size_t i) { + char **block = s->cands_top[i >> CANDS_BLOCK_SHIFT]; + return block ? block[i & CANDS_BLOCK_MASK] : NULL; +} + static void test_async_reader_basic(void) { int pfd[2]; CHECK(pipe(pfd) == 0); @@ -296,9 +305,9 @@ static void test_async_reader_basic(void) { async_reader((void *)s); CHECK(s->count == 3); - CHECK(strcmp(s->cands[0], "alpha") == 0); - CHECK(strcmp(s->cands[1], "beta") == 0); - CHECK(strcmp(s->cands[2], "gamma") == 0); + CHECK(strcmp(cands_at(s, 0), "alpha") == 0); + CHECK(strcmp(cands_at(s, 1), "beta") == 0); + CHECK(strcmp(cands_at(s, 2), "gamma") == 0); free_async_session(s); } @@ -319,13 +328,15 @@ static void test_async_reader_ansi_stripping(void) { async_reader((void *)s); CHECK(s->count == 2); - CHECK(strcmp(s->cands[0], "file.txt") == 0); - CHECK(strcmp(s->cands[1], "plain.c") == 0); + CHECK(strcmp(cands_at(s, 0), "file.txt") == 0); + CHECK(strcmp(cands_at(s, 1), "plain.c") == 0); free_async_session(s); } -static void test_async_reader_buffer_growth(void) { - /* Initial cap=4, write 32 lines — exercises the realloc doubling path. */ +static void test_async_reader_many_lines(void) { + /* Write 32 lines. Under the chunked-storage design no realloc is + involved; this just verifies sequential round-trip through the + accessor. */ enum { NLINES = 32 }; int pfd[2]; CHECK(pipe(pfd) == 0); @@ -345,11 +356,64 @@ static void test_async_reader_buffer_growth(void) { char expected[32]; for (int i = 0; i < NLINES; i++) { snprintf(expected, sizeof expected, "line%d", i); - CHECK(strcmp(s->cands[i], expected) == 0); + CHECK(strcmp(cands_at(s, i), expected) == 0); } + /* All 32 fit in block 0; later blocks must be untouched. */ + CHECK(s->cands_top[0] != NULL); + CHECK(s->cands_top[1] == NULL); free_async_session(s); } +/* ===================================================================== + * Chunked candidate storage — index split formula and accessor + * ===================================================================== */ + +static void test_cands_top_index_split(void) { + /* Verify hi = i >> SHIFT and lo = i & MASK match the documented + "i = hi * BLOCK_SIZE + lo" decomposition. */ + size_t cases[][3] = { + /* { i, expected_hi, expected_lo } */ + { 0, 0, 0 }, + { 1, 0, 1 }, + { CANDS_BLOCK_SIZE - 1, 0, CANDS_BLOCK_SIZE - 1 }, + { CANDS_BLOCK_SIZE , 1, 0 }, + { CANDS_BLOCK_SIZE + 5, 1, 5 }, + { CANDS_BLOCK_SIZE * 2 , 2, 0 }, + { CANDS_BLOCK_SIZE * 2 + 7, 2, 7 }, + { CANDS_BLOCK_SIZE * 100 , 100, 0 }, + }; + for (size_t k = 0; k < sizeof cases / sizeof *cases; k++) { + size_t i = cases[k][0]; + CHECK((i >> CANDS_BLOCK_SHIFT) == cases[k][1]); + CHECK((i & CANDS_BLOCK_MASK) == cases[k][2]); + /* Inverse: reconstruct i from (hi, lo). */ + CHECK((cases[k][1] << CANDS_BLOCK_SHIFT) + cases[k][2] == i); + } +} + +static void test_cands_top_accessor_reads_block_pointer(void) { + /* Manually populate a single slot via the accessor formula and + verify the read path returns the same pointer. */ + AsyncSession *s = calloc(1, sizeof *s); + CHECK(s != NULL); + pthread_mutex_init(&s->mu, NULL); + + /* Allocate block 3 and write a sentinel pointer at slot 42. */ + size_t hi = 3, lo = 42; + s->cands_top[hi] = calloc(CANDS_BLOCK_SIZE, sizeof *s->cands_top[hi]); + CHECK(s->cands_top[hi] != NULL); + char *sentinel = "sentinel"; + s->cands_top[hi][lo] = sentinel; + + /* Read it back via the accessor formula at the equivalent global index. */ + size_t i = (hi << CANDS_BLOCK_SHIFT) + lo; + CHECK(s->cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK] == sentinel); + + free(s->cands_top[hi]); + pthread_mutex_destroy(&s->mu); + free(s); +} + /* ===================================================================== * Result cache — phase 1: exact-match lookup, LRU eviction, MRU touch * ===================================================================== */ @@ -762,7 +826,11 @@ int main(void) { printf("--- async_reader ---\n"); RUN(test_async_reader_basic); RUN(test_async_reader_ansi_stripping); - RUN(test_async_reader_buffer_growth); + RUN(test_async_reader_many_lines); + + printf("--- chunked cands_top ---\n"); + RUN(test_cands_top_index_split); + RUN(test_cands_top_accessor_reads_block_pointer); printf("--- cache (phase 1: exact-match) ---\n"); RUN(test_cache_lookup_miss_on_empty); diff --git a/fzf-native-module.c b/fzf-native-module.c index 10bbdac..3c28b0d 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -827,10 +827,31 @@ emacs_value fzf_native_make_slab(emacs_env *env, #if defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) -#define ASYNC_INIT_CAP 4096 #define ASYNC_LINE_MAX 8192 #define ARENA_CHUNK_SIZE (4 * 1024 * 1024) /* 4 MB per chunk */ +/* Chunked candidate-pointer storage. + * + * The candidate-pointer table is split into fixed-size blocks owned by a + * top-level pointer table. The reader appends to the current block; when a + * block fills, it allocates the next one. No realloc ever moves pointer + * data, so the worst-case allocation the reader performs is a single + * block — predictable cost regardless of pool size. + * + * cands_top[] : CANDS_TOP_CAP slots × 8 B (~32 KB, fixed inline) + * cands_top[i] : CANDS_BLOCK_SIZE × 8 B (2 MB, on demand) + * + * Defaults: 256K pointers per block, 4096 blocks → 1 G candidates max. + * + * Index split: hi = i >> SHIFT (which block) + * lo = i & MASK (which slot in that block) + * Both ops are single CPU instructions because BLOCK_SIZE is a power of 2. + */ +#define CANDS_BLOCK_SHIFT 18 +#define CANDS_BLOCK_SIZE ((size_t)1 << CANDS_BLOCK_SHIFT) +#define CANDS_BLOCK_MASK (CANDS_BLOCK_SIZE - 1) +#define CANDS_TOP_CAP 4096 + /* Arena allocator: strings are packed into large chunks so freeing the entire candidate set is O(chunks) instead of O(candidates). */ typedef struct ArenaChunk { struct ArenaChunk *next; size_t used; char data[]; } ArenaChunk; @@ -1241,9 +1262,12 @@ typedef struct { pthread_mutex_t mu; Arena arena; /* backing storage for all candidate strings */ - char **cands; + /* Two-level pointer table; see CANDS_BLOCK_SHIFT comments above. + Top level is fixed-size and zero-initialized at session start; + blocks are allocated on demand by the reader. Access pattern: + cands_top[i >> CANDS_BLOCK_SHIFT][i & CANDS_BLOCK_MASK]. */ + char **cands_top[CANDS_TOP_CAP]; size_t count; - size_t cap; _Atomic int gen; size_t last_filtered; /* candidates matching last filter */ @@ -1302,19 +1326,47 @@ static void *async_reader(void *arg) { } } + /* Compute chunked-table coordinates BEFORE allocating in the arena. + The reader is the sole writer to s->count, so reading without the + lock is safe. Cap check first so a hit doesn't leak arena bytes + on every dropped line — at 1G candidates we keep draining the pipe + (so the child doesn't block on write) but allocate nothing. */ + size_t i = s->count; + size_t hi = i >> CANDS_BLOCK_SHIFT; + size_t lo = i & CANDS_BLOCK_MASK; + if (hi >= CANDS_TOP_CAP) { + /* Verbose by design: a single fzf-async session producing >1G + candidates is so far outside expected behavior that hitting this + almost certainly means a broken upstream command (infinite loop, + runaway find on a cyclic FS, etc.). Log every dropped line so + the cause is obvious in the log. */ + size_t preview = len > 80 ? 80 : len; + fzf_log("async_reader: TOP TABLE FULL count=%zu cap=%zu line='%.*s%s'\n", + s->count, (size_t)CANDS_TOP_CAP * CANDS_BLOCK_SIZE, + (int)preview, line, len > preview ? "..." : ""); + continue; + } + char *dup = arena_strdup(&s->arena, line, len); if (!dup) continue; - pthread_mutex_lock(&s->mu); - if (s->count >= s->cap) { - size_t ncap = s->cap * 2; - fzf_log("async_reader: reallocating candidates %zu -> %zu\n", s->cap, ncap); - char **nc = realloc(s->cands, ncap * sizeof *nc); - if (!nc) { free(dup); pthread_mutex_unlock(&s->mu); continue; } - s->cands = nc; - s->cap = ncap; + /* Pre-allocate the new block (if needed) OUTSIDE s->mu — that's the + largest allocation the reader ever does (2 MB). Doing it under the + lock would let a slow malloc (e.g. macOS compressor pressure) stall + the scoring thread's snapshot path. */ + char **block = s->cands_top[hi]; /* sole writer ⇒ safe to read unlocked */ + bool need_publish = (block == NULL); + if (need_publish) { + block = malloc(CANDS_BLOCK_SIZE * sizeof *block); + if (!block) continue; /* dup stays in arena, freed at session stop */ + fzf_log("async_reader: allocated block %zu (count=%zu, %zu MB)\n", + hi, s->count, (CANDS_BLOCK_SIZE * sizeof *block) >> 20); } - s->cands[s->count++] = dup; + + pthread_mutex_lock(&s->mu); + if (need_publish) s->cands_top[hi] = block; /* publish under lock */ + s->cands_top[hi][lo] = dup; + s->count++; pthread_mutex_unlock(&s->mu); atomic_fetch_add_explicit(&s->gen, 1, memory_order_relaxed); } @@ -1359,7 +1411,8 @@ static void async_session_destroy(void *ptr) { if (s->pid > 0) { waitpid(s->pid, NULL, 0); s->pid = -1; } pthread_mutex_lock(&s->mu); arena_free(&s->arena); - free(s->cands); + for (size_t k = 0; k < CANDS_TOP_CAP; k++) + if (s->cands_top[k]) { free(s->cands_top[k]); s->cands_top[k] = NULL; } pthread_mutex_unlock(&s->mu); pthread_mutex_destroy(&s->mu); free(s); @@ -1521,8 +1574,8 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, s->pid = pid; s->fp = fdopen(pfd[0], "r"); - s->cap = ASYNC_INIT_CAP; - s->cands = malloc(s->cap * sizeof *s->cands); + /* cands_top is zero-initialized by the calloc above; blocks are + allocated lazily by the reader on first write into each block. */ pthread_mutex_init(&s->mu, NULL); pthread_mutex_init(&s->score_req_mu, NULL); pthread_cond_init(&s->score_req_cond, NULL); @@ -1562,7 +1615,7 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, cache_init(&s->cache, cache_max); } - if (!s->fp || !s->cands || + if (!s->fp || pthread_create(&s->reader, NULL, async_reader, s) != 0 || pthread_create(&s->score_thread, NULL, scoring_thread_fn, s) != 0) { async_session_destroy(s); @@ -1730,20 +1783,38 @@ static void *scoring_thread_fn(void *arg) { /* Cap delta range too in case reader shrank. */ if (refine_delta_from > count) refine_delta_from = count; delta_len = count - refine_delta_from; - /* Refine entries first: validate each refine_idx[i] < count. */ + /* Refine entries first: validate each refine_idx[i] < count. + Random-access path — pay shift+mask per entry. */ size_t w = 0; for (size_t i = 0; i < refine_idx->count; i++) { uint32_t gi = refine_idx->idx[i]; - if (gi < count) { snap[w] = s->cands[gi]; snap_idx[w] = gi; w++; } + if (gi < count) { + snap[w] = s->cands_top[gi >> CANDS_BLOCK_SHIFT][gi & CANDS_BLOCK_MASK]; + snap_idx[w] = gi; + w++; + } } - /* Delta entries. */ + /* Delta entries — sequential range, but a small one (delta size). + Walk via accessor too; the cost is negligible at delta-scale. */ for (size_t k = 0; k < delta_len; k++) { size_t gi = refine_delta_from + k; - snap[w] = s->cands[gi]; snap_idx[w] = (uint32_t)gi; w++; + snap[w] = s->cands_top[gi >> CANDS_BLOCK_SHIFT][gi & CANDS_BLOCK_MASK]; + snap_idx[w] = (uint32_t)gi; + w++; } snap_count = w; } else if (snap) { - memcpy(snap, s->cands, count * sizeof *snap); + /* Full-pool snapshot — walk block-by-block. Inside a block this is + a flat memcpy over contiguous memory, same speed as the old flat + array. Boundary-crossing cost is paid once per block (every + CANDS_BLOCK_SIZE entries — ~250 crossings for a 60M pool). */ + size_t copied = 0; + for (size_t hi = 0; copied < count; hi++) { + size_t in_block = CANDS_BLOCK_SIZE; + if (count - copied < in_block) in_block = count - copied; + memcpy(snap + copied, s->cands_top[hi], in_block * sizeof *snap); + copied += in_block; + } for (size_t i = 0; i < count; i++) snap_idx[i] = (uint32_t)i; snap_count = count; } From 34ab2a77b38dc215c1ff9e48a9c7f09a1708e134 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sun, 10 May 2026 10:17:18 -0400 Subject: [PATCH 41/47] Keep [FILTERED](TOTAL) overlay in sync with what's displayed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related freshness fixes for the prompt overlay during cache hits. Without these, the displayed counts could lag behind the visible state by several seconds — visible to the user as the prompt showing e.g. [27815204](46291615) for ~3 seconds while they type past the prefix into a longer query against a streaming pool that has grown to 55M. A. last_total now tracks the *current* pool, not the pool size at scoring-publish time. Without this, the TOTAL displayed in the prompt freezes at the last scored value, lagging behind the streaming counter visible elsewhere. Cheap fix: the dispatch path already reads s->count under s->mu for the pool-size check; piggy- back a write of s->last_total under score_res_mu in the same call. B. last_filtered on cache hits is now set from the cached entry's full match-set count (m_idx->count) — which describes the candidate set the user is actually looking at right now (the cached top-K we just returned). Previously last_filtered held whatever the most recent scoring run published, which during prefix hits could be a completely different query's count. For OR-query entries (m_idx == NULL, can't be refinement sources) we fall back to top_count. On cache miss we leave last_filtered alone — scoring will publish a fresh value shortly, and meanwhile the existing value is at least consistent with the score_results we're falling back to display. The race with the scoring thread is benign: scoring publishes its authoritative values before bumping gen, so any time-ordering of our write vs scoring's write that ends with scoring last (the common case) results in scoring's fresh values being shown. The other ordering shows our cache-derived values briefly until the next gen bump triggers a re-display. --- fzf-native-module.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 3c28b0d..09a789a 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -2021,14 +2021,28 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, } pthread_mutex_unlock(&s->score_req_mu); - /* Determine display set. Cache hits return their (stale-but-useful) - top-K immediately; misses fall back to whatever the scorer last - published. */ + /* Update displayed stats so the [FILTERED](TOTAL) overlay matches what + we're about to return: + - TOTAL always reflects the *current* pool, not the pool at the + last scoring time. Without this, the total in the prompt lags + behind the streaming counter visible elsewhere — and on cache + hits (which skip scoring) it can stay stale for many seconds. + - FILTERED on a cache hit is the cached entry's full match-set + count (m_idx->count), which describes the candidate set the + user is currently looking at. On a miss we leave it alone: + scoring will publish a fresh value shortly, and meanwhile the + existing value (from the previous query) is at least + consistent with the candidates we're about to fall back to. */ ScoredStr *snap = NULL; size_t rcount = 0; if (exact_hit || prefix_hit) { snap = cached_top; /* ownership transferred from cache_lookup_*. */ rcount = cached_count; + size_t cached_filtered = cached_m_idx ? cached_m_idx->count : rcount; + pthread_mutex_lock(&s->score_res_mu); + s->last_filtered = cached_filtered; + s->last_total = current_pool; + pthread_mutex_unlock(&s->score_res_mu); } else { pthread_mutex_lock(&s->score_res_mu); rcount = s->score_count; @@ -2037,6 +2051,7 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, memcpy(snap, s->score_results, rcount * sizeof *snap); else rcount = 0; + s->last_total = current_pool; /* keep TOTAL live even on miss */ pthread_mutex_unlock(&s->score_res_mu); } From f783ae5cf894864c80a7cdb3f02d75f368c6c566 Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sun, 10 May 2026 10:17:19 -0400 Subject: [PATCH 42/47] Make C reads canonical: fzf-native-{batch,async}-* defcustoms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the C module reads four knobs via symbol-value from three different package namespaces: fussy-fzf-native-highlight (fussy) fzf-async-highlight (fzf-async) fzf-async-max-line-length (fzf-async) fzf-async-cache-size (fzf-async) This leaks the layering the wrong direction — the lowest-level package shouldn't have to know symbol names from two higher-level packages. Move all four to the fzf-native namespace: fzf-native-batch-highlight (sync path, default 25) fzf-native-async-highlight (async path, default 200) fzf-native-max-line-length (async reader, default t) fzf-native-async-cache-size (async start, default 40) C reads now hit only fzf-native-* names. Higher-level packages keep their existing user-facing defcustoms and bridge the values onto these canonical names — fussy via setq-local (synchronous, same-buffer call pattern) and fzf-async via :around advice on the C entry points (timer-driven, cross-buffer). Those bridges land in the respective package commits. Naming convention: - "batch-" prefix marks the synchronous score/score-all path. - "async-" prefix marks the streaming async path. - max-line-length has no prefix because it conceptually belongs to the line-stream itself; kept short. The companion fzf-async / fussy bridge commits make this fully backward-compatible — users continue to set fussy-fzf-native-highlight or fzf-async-highlight as before, and the canonical name picks up the package-specific value at call time. --- fzf-native-module.c | 21 ++++++++++------ fzf-native.el | 59 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 09a789a..0457ed0 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -379,7 +379,9 @@ static fzf_case_types resolve_fzf_native_case_mode(emacs_env *env) { LEN — highlight all (t). N — highlight top N (clamped to LEN). */ static size_t resolve_fussy_highlight_cap(emacs_env *env, size_t len) { - emacs_value sym = env->intern(env, "fussy-fzf-native-highlight"); + /* Canonical name; fussy bridges its `fussy-fzf-native-highlight' + onto this via `setq-local' inside its all-completions entry. */ + emacs_value sym = env->intern(env, "fzf-native-batch-highlight"); emacs_value v = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { env->non_local_exit_clear(env); @@ -1584,7 +1586,9 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, atomic_store(&s->score_abort, false); { - emacs_value sym = env->intern(env, "fzf-async-max-line-length"); + /* Canonical name; fzf-async bridges `fzf-async-max-line-length' + onto this via :around advice on `fzf-native-async-start'. */ + emacs_value sym = env->intern(env, "fzf-native-max-line-length"); emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) env->non_local_exit_clear(env); @@ -1601,7 +1605,9 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, { size_t cache_max = 40; - emacs_value sym = env->intern(env, "fzf-async-cache-size"); + /* Canonical name; fzf-async bridges `fzf-async-cache-size' + onto this via :around advice on `fzf-native-async-start'. */ + emacs_value sym = env->intern(env, "fzf-native-async-cache-size"); emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) env->non_local_exit_clear(env); @@ -2055,15 +2061,16 @@ fzf_native_async_candidates(emacs_env *env, ptrdiff_t nargs, pthread_mutex_unlock(&s->score_res_mu); } - /* Resolve C-side highlight cap from defcustoms fzf-async-highlight and - fzf-async-highlight-max-candidates. Both are read via symbol-value so - the user can change them without reloading the module. */ + /* Resolve C-side highlight cap from the canonical defcustom. Read + via symbol-value so the user can change it without reloading the + module. fzf-async bridges `fzf-async-highlight' onto this via + :around advice on `fzf-native-async-candidates'. */ size_t hl_cap = 0; fzf_pattern_t *hl_pattern = NULL; fzf_slab_t *hl_slab = NULL; if (filter_for_hilit) { - emacs_value sym_hi = env->intern(env, "fzf-async-highlight"); + emacs_value sym_hi = env->intern(env, "fzf-native-async-highlight"); emacs_value hi = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym_hi); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) env->non_local_exit_clear(env); diff --git a/fzf-native.el b/fzf-native.el index 82e8887..c4bf607 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -55,6 +55,12 @@ confirmation before compiling." :type 'boolean :group 'fzf-native) +;; Canonical knobs the C module reads via `symbol-value' at call time. +;; Higher-level packages (fzf-async, fussy) keep their own user-facing +;; defcustoms and bridge their values onto these names — fussy via +;; `setq-local' (synchronous, same-buffer call pattern), fzf-async via +;; `:around' advice on the C entry points (timer-driven, cross-buffer). + (defcustom fzf-native-case-mode 'smart "How fzf-native treats letter case when matching queries. smart Case-insensitive when the query is all lowercase; case-sensitive @@ -68,6 +74,59 @@ Read on every scoring call; changes take effect immediately." (const :tag "Respect case" respect)) :group 'fzf-native) +(defcustom fzf-native-batch-highlight 25 + "Highlight cap for the synchronous (batch) scoring path. +Read by `fzf-native-score' / `fzf-native-score-all' on every call. +nil disables highlighting; a positive integer caps the number of +top-scoring candidates that get `completions-common-part' face +applied via `fzf_get_positions' inside the C module. + +Bridged by fussy from `fussy-fzf-native-highlight' via `setq-local'." + :type '(choice (const :tag "Disabled" nil) + (integer :tag "Top N candidates")) + :group 'fzf-native) + +(defcustom fzf-native-async-highlight 200 + "Highlight cap for the streaming (async) candidate path. +Read by `fzf-native-async-candidates' on every call. Same semantics +as `fzf-native-batch-highlight' (nil / positive integer). + +Bridged by fzf-async from `fzf-async-highlight' via `:around' advice." + :type '(choice (const :tag "Disabled" nil) + (const :tag "All candidates" t) + (integer :tag "Top N candidates")) + :group 'fzf-native) + +(defcustom fzf-native-max-line-length t + "Per-line character cap applied by the async reader thread. +nil — no limit. +t — apply a built-in default of 512 characters. +positive N — exclude lines longer than N characters. +negative -N — include but truncate lines to N characters. + +Read once at session start by `fzf-native-async-start'. + +Bridged by fzf-async from `fzf-async-max-line-length' via `:around' +advice; the read happens inside `fzf-native-async-start' so the +advice is in scope for the symbol-value lookup." + :type '(choice (const :tag "No limit" nil) + (const :tag "Default (512)" t) + (integer :tag "N (positive = exclude, negative = truncate)")) + :group 'fzf-native) + +(defcustom fzf-native-async-cache-size 40 + "Per-session LRU result cache capacity for the async path. +Each entry stores top-K results and the full matched-candidate index +for one query — enables exact-fresh hits (skip scoring) and prefix- +refinement hits (rescore only previously-matched candidates plus +deltas) without re-scanning the full pool. + +Read once at session start by `fzf-native-async-start'. + +Bridged by fzf-async from `fzf-async-cache-size' via `:around' advice." + :type 'integer + :group 'fzf-native) + (defun fzf-native-module--cmake-is-available () "Return t if cmake is available. CMake is needed to build fzf-native, here we check that we can find From 98913f5022362c3eb9c85ed511ded795445a1290 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 15:01:44 +0000 Subject: [PATCH 43/47] Update binaries for all platforms --- bin/Darwin/arm64/fzf-native-module.so | Bin 71976 -> 72168 bytes bin/FreeBSD/fzf-native-module.so | Bin 56848 -> 59864 bytes bin/Linux/fzf-native-module.so | Bin 65880 -> 66072 bytes bin/Windows/Release/fzf-native-module.dll | Bin 31232 -> 33280 bytes bin/Windows/Release/fzf-native-module.exp | Bin 3763 -> 3910 bytes bin/Windows/Release/fzf-native-module.lib | Bin 6766 -> 7022 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index aa953b43dd9352ec794dc085420b42116e525901..3d6f482712cde13818f62f698359591bfe3d8de4 100755 GIT binary patch delta 18642 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 P_0v6!nsB!BY?uEBFEEjP diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index 4eb8e037627517f1a4f38f941bfa46b14642e429..6be8a060b1db758bdd8c6bb935ed38b94405b81b 100755 GIT binary patch delta 27539 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 diff --git a/bin/Windows/Release/fzf-native-module.dll b/bin/Windows/Release/fzf-native-module.dll index 4308e833455ce8c499b9cfc219847729af09c873..94a40d9b9603352b1ef364651798ae0944a9b0ce 100644 GIT binary patch delta 12379 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 diff --git a/bin/Windows/Release/fzf-native-module.exp b/bin/Windows/Release/fzf-native-module.exp index 2bdd6c9126bdff95156ec0d46f4584dc1e511b47..ebec96b6614a3d1b7ef290d100df76a170fa0c95 100644 GIT binary patch literal 3910 zcmeHKTWB0r82;1k{hIWa^rBsD?M2(o?q+jotrBdlmBuzSrRHH^af@Ij%7prD9?4GK`ObH~ z|I9ga&d&dxiE(-k{e^OF5zznzC1o1QSG5MJn{$W^)T4<&%b^;?U&=7YtHmuNfl9P< zF45S$uocvU)cyc7o+FI3fppcbQ2TGxZ8V1xG?(Vld|E&YX%Q`^CA5^5(Q;Zr?bJal zX%(%eHMEvGsW4sGRaU+=Y3?dHM%6Y`hHaCsTb|O@q_S)^ZkxJc7F5;t$tGjS;^QqlDFO3Ns$czMSS<}Ilc^#$nlu|=)7f*sH&tm1@?%hwe)ftOYb%sxhQ zFgjFY{#q{)>f=_F>$UC%mY^%wRi|X$ifb0Esir;ks)dG7v8zR^jNeFqXn07^J0)zZ zh2M$zDfEI>R<<>5;x{L}2ZXm_Bao9KK}q0zVOxL;glz{Vg`EK|6t)w1hp=-%Bqr`n z1Md{}B5<)V&cn^ZUIH!=#yQNRC+KA$KShG}0`C&`8gQAgLEu(lZvdAI+Yj6(>>c0= zVF!Wyj0t)d*e+}cxI@?lV27|_;N8M50#^z<2E0eu`@mJgMu9tpeE?i7Oatx`b_uvf z*cdP+>|@|sVFs{U*r&iwVH3c#u+M>A!faqh*q6X{!YaV5u&;sZh55i9Vc!5Z2%7@t zgk1)16!ti=X6_@OEL(0Q-eq18x#F7x*i#SK*z9sEs#^_V4Ld-HqgL zrsGz*`%$ZGI48aCqo!>t&uj{ZHepUxu(5kP*kX7&L0OKCedR3CBUavZJg49%w;$b+ z9D4ZhA)2B4Iw*hAz|@-38dEpZ@v3DTWKNm1Z;v+YIz?BNk_Rl?^t8r^$1M+jlN|A@ zhUIA4bT+F!Sca=qT+1`Hr}kMo$>Mn%`DJbzA$VTCi_;@94+cD3=ejsYIEpvcPfdeh}%uDaY>vT(-=84!QTJ4%{LfVdtzfF>f(?5-E`|--v zYtOZR_UD^te|~yw?cGL5%N;XZeN0bb`7g4fwDWdeyR}m#g*Xs(FBHtWbb3p)w&##Y#BJ literal 3763 zcmeHKU1%It6h6uH|0hlQleV_nwT;@pY<4%BG%Zy^TeZ>{OH*oBTRM zUL2KFK2E)dnDH2qr3++Z^NY0qcHTwZq|qW;OiO4fEu-bMf>zQhT1`E)hI*-w*3vp! zPaCM8%8l}1)eN0Ed$8hJb=S^Xu1kjD1g2rmnT~5tyS8E3WwY*vWLL~mV5o|QQ*}aa zZ`d!?P1mTHVQE&C=gm^6%0|^h*P@nv+cs)u7}|a{CU}7pI$kwsO994IrFx@ruG!CA zJ}nhQnz2Kp*3kKRajN(!jK_~p7$vWQ-E;7B5YNIWJ5|$l8a7Tk zjw=p3e)FwBKJpr6fJ=l80GA5e3EU>^JaCz?y}*pHi$J&?+U9`w3ws^7LRb;FUDzAI zmBPk=JA_>V@+>qu2;>npx(r+`Y#jK2u(yCc!j1qR6!s2qjj*G@hlITc>=iZvjm?CxN?#eFR)5>@;wXuup*Nh3UY(!afCV5cWK9P}p@~zc34!74`*? zEw$IOxI z+w(PS*ug#y8az#vdaz+vUnP3dDfwRDmBY-=6T31KPfQ-8EA((5mCjliS~vCf&~Z9l zcU+6?d7BO&(2x7xjBi#lk2$U#=(j;9xLg@XQQ6_KLmJAtjAI;@}c zJQoe7K=Z|Hv6vkmDdhI)SeJg(3&PW*MSUKh>r~HVc`R(xSRV(%yM?k%u;V7VLrCj~ zh3}SRXxj>%w_Ws9h}#G&yB=FRLfvK<*c01^R1B;kb{EQ{365XdI>QE3o2 z6>uEK`q)+3)EN?a6n5CiqGA@WW>r&7$31St&dY7up>hXQc3)QGQL%YFsOY){mDrz? zm1J$aOTC>&H63onhd9=%9ZGp^?B;Cj45>U%c6PC}LMxBL?k;LP#Ik{Xo(&zLb{h=+ zI4yS#xjYa@fkfz~KcVf!$FGQtHWw^3Zck2LU+ee(^30wuPk(#q?VAg?-n@COwB+SO z6Ca?`ZPc5<_m3VEbD_vREyvfJ&a1;!JwFWa=qb}UUkzEuf|GXfyGEn&vTcUhT&@@A zNGb7G6wg(o_paw`&e^AYi|E&uGBBA# z%F{rrAEgxNU_u$D$%Im*=Mu^Yg$ZSpUP&nX=yF0CqxTcae)_Ca$>-=sLdnz538g^4 zCzN68#&$u(j>)?6Me0o`BQ%&$M(I#O*+)|eWsGJM%6@8eDuot^H0}Is?PS5GMrM5rj%z>%4|wGmr`C!DerbDAER`)E9?K) Tcl4h@{IM;;JTCmZukXJB!Qc>rz`O>m#W;=wEea-p722X& z5*X543g}Z=2YOUY16`VC>ut)JOq#HG7a2D1qM!rpP?Pv8AbUzqv5|oz-zkSeHk{7* zo`(VLU)^h+^~lS=mO6D&kIJ^KPfmp!-;VG`4dMSk!OHTF|MW>&M delta 596 zcmYk&Pe>GD7zXfX)^WFQW}VqT^Ivdv6$}N#wKlqW(nCQ|dyu*m!5%7DCA);Muym3= z1njL`&9cJS6N^u<+IAT(%nd&pC+(_*)$yrM6Fv+ba?(){V;>`gA ztUdCpscj|&UTTv`VJ3CXOvI+v$;g?i%7q${M?;4E%;hA~4H-AgIMUa&cJ0>uziA_m z+ckjwJTPW)ZFBYl(4kXWF9Ju@ECGFLlz}~3a)2GGyFicTc&r{(ePEls0NA8V2%OU! z;%|k948+RL3`GdV&d$cqvt5m+E&X@W3Av|_%7z}q-D?dkK2uNg()S$mgZ=Sjf!`Q` zE{;wtx+lKBEPdZGE5CDJrrEdIuNbaa!HRmYd-6GXw;-+Hm71)y z53aK+%J-n9W@?46Q>;qzEPSC>AHMvX>@Lf Date: Sun, 10 May 2026 12:02:58 -0400 Subject: [PATCH 44/47] Drop `t' sentinel from fzf-native-max-line-length; default is 256 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The defcustom previously used `t' as a sentinel meaning "use the built-in default of 512", with the actual integer hardcoded in C. Two related cleanups: 1. Make the defcustom value the actual integer. No `t' sentinel, no hardcoded fallback in C. Type is `(choice nil integer)': nil -> no limit positive N -> exclude lines longer than N characters negative -N -> include but truncate lines to N characters 2. Drop the `else if (env->eq(env, val, Qt)) s->max_line_length = 256' branch in `fzf_native_async_start'. The C side now reads the integer directly via `extract_integer'; the default lives where it should — in fzf-native.el as the defcustom's :type/value, not as a magic fallback in the dynamic module. --- fzf-native-module.c | 7 ++++--- fzf-native.el | 6 ++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/fzf-native-module.c b/fzf-native-module.c index 0457ed0..5a6fa53 100644 --- a/fzf-native-module.c +++ b/fzf-native-module.c @@ -1587,13 +1587,14 @@ fzf_native_async_start(emacs_env *env, ptrdiff_t nargs, { /* Canonical name; fzf-async bridges `fzf-async-max-line-length' - onto this via :around advice on `fzf-native-async-start'. */ + onto this via :around advice on `fzf-native-async-start'. + Type is integer (positive = exclude, negative = truncate) or nil + (no limit). The defcustom default lives in fzf-native.el — no + hardcoded fallback here. */ emacs_value sym = env->intern(env, "fzf-native-max-line-length"); emacs_value val = env->funcall(env, env->intern(env, "symbol-value"), 1, &sym); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) env->non_local_exit_clear(env); - else if (env->eq(env, val, Qt)) - s->max_line_length = 512; else if (!env->eq(env, val, Qnil)) { s->max_line_length = (ptrdiff_t)env->extract_integer(env, val); if (env->non_local_exit_check(env) != emacs_funcall_exit_return) { diff --git a/fzf-native.el b/fzf-native.el index c4bf607..2595157 100644 --- a/fzf-native.el +++ b/fzf-native.el @@ -97,10 +97,9 @@ Bridged by fzf-async from `fzf-async-highlight' via `:around' advice." (integer :tag "Top N candidates")) :group 'fzf-native) -(defcustom fzf-native-max-line-length t +(defcustom fzf-native-max-line-length 256 "Per-line character cap applied by the async reader thread. -nil — no limit. -t — apply a built-in default of 512 characters. +nil — no limit. positive N — exclude lines longer than N characters. negative -N — include but truncate lines to N characters. @@ -110,7 +109,6 @@ Bridged by fzf-async from `fzf-async-max-line-length' via `:around' advice; the read happens inside `fzf-native-async-start' so the advice is in scope for the symbol-value lookup." :type '(choice (const :tag "No limit" nil) - (const :tag "Default (512)" t) (integer :tag "N (positive = exclude, negative = truncate)")) :group 'fzf-native) From 32d3a3560b024180b12cdefacf15aee6e7e8d565 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 10 May 2026 18:33:39 +0000 Subject: [PATCH 45/47] Update binaries for all platforms --- bin/Darwin/arm64/fzf-native-module.so | Bin 72168 -> 72168 bytes bin/FreeBSD/fzf-native-module.so | Bin 59864 -> 59832 bytes bin/Linux/fzf-native-module.so | Bin 66072 -> 66072 bytes bin/Windows/Release/fzf-native-module.dll | Bin 33280 -> 33280 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/Darwin/arm64/fzf-native-module.so b/bin/Darwin/arm64/fzf-native-module.so index 3d6f482712cde13818f62f698359591bfe3d8de4..532f725ed6c63c7448479692aacc4bf79d5c01b7 100755 GIT binary patch delta 3321 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 L)0@vIaOA%MnDAj; diff --git a/bin/FreeBSD/fzf-native-module.so b/bin/FreeBSD/fzf-native-module.so index e5b993bca40ab7e03fc870245d85c88fd54f0da1..a4dae7793b582f97a8499cff355652520980867e 100755 GIT binary patch delta 6256 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 delta 6252 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 diff --git a/bin/Linux/fzf-native-module.so b/bin/Linux/fzf-native-module.so index 6be8a060b1db758bdd8c6bb935ed38b94405b81b..7584d52d096dde9cc8ac3b1e381373084d6980d7 100755 GIT binary patch delta 5839 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 delta 5928 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 Date: Sun, 10 May 2026 16:04:16 -0400 Subject: [PATCH 46/47] Update readme & architecture --- README.org | 25 +++++++++++++++++++++++++ architecture.org | 31 +++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/README.org b/README.org index b86417a..141b7f1 100644 --- a/README.org +++ b/README.org @@ -110,6 +110,31 @@ stream candidates from long-running shell commands (~find~, ~rg~, [[https://github.com/jojojames/fzf-async/blob/HEAD/architecture.org][fzf-async's architecture overview]] for the Elisp-side pipeline. +** Customization + +The C module reads the following defcustoms via ~symbol-value~ at call +time. Higher-level packages (~fussy~, ~fzf-async~) keep their own +user-facing defcustoms and bridge their values onto these canonical +names — ~fussy~ via ~setq-local~ (synchronous, same-buffer call +pattern), ~fzf-async~ via ~:around~ advice on the C entry points +(timer-driven, cross-buffer). Set these directly only if you call +~fzf-native-score~ / ~fzf-native-score-all~ / ~fzf-native-async-*~ +yourself; otherwise prefer the package-level knob. + +| Variable | Default | Read by | Behavior | +|---------------------------------+---------+------------------------------------+---------------------------------------------| +| ~fzf-native-case-mode~ | ~smart~ | every scoring call (sync + async) | ~smart~ (lowercase = ignore, mixed = respect) / ~ignore~ / ~respect~ | +| ~fzf-native-batch-highlight~ | 25 | ~fzf-native-score~ / ~fzf-native-score-all~ | nil = off; positive N = apply ~completions-common-part~ face to top N candidates via ~fzf_get_positions~ in C. | +| ~fzf-native-async-highlight~ | 200 | ~fzf-native-async-candidates~ | nil = off; t = all returned candidates; positive N = top N. | +| ~fzf-native-max-line-length~ | 256 | ~fzf-native-async-start~ (once at session start) | nil = no limit; +N = drop lines longer than N; -N = truncate lines to N characters. | +| ~fzf-native-async-cache-size~ | 40 | ~fzf-native-async-start~ (once at session start) | LRU result-cache entries. Each entry stores top-K results plus the full matched-candidate index for one query, enabling exact-fresh hits and prefix-refinement hits. | + +Highlighting runs entirely inside the C module: after scoring, +~fzf_get_positions~ produces matched byte offsets, which are merged +into runs and applied to the candidate string with ~put-text-property~ +before strings are handed back to Emacs. No Elisp regex pass is +involved. + ** Building the Native Libraries #+begin_src bash diff --git a/architecture.org b/architecture.org index 289390e..34e693e 100644 --- a/architecture.org +++ b/architecture.org @@ -47,7 +47,7 @@ process and must never block the main thread. | Function | Args | Returns | Path | |--------------------------------+--------------------------------------+------------------------+-------------| -| =fzf-native-score= | =(str query &optional slab)= | =(score idx idx ...)= | sync single | +| =fzf-native-score= | =(str query &optional slab)= | =(score)= | sync single | | =fzf-native-score-all= | =(collection query &optional slab)= | sorted candidate vector | sync batch | | =fzf-native-make-default-slab= | =()= | slab | (helper) | | =fzf-native-make-slab= | =(size16 size32)= | slab | (helper) | @@ -133,17 +133,22 @@ the fzf-native backend. Elisp call: (fzf-native-score "foobar" "fb" slab) │ ▼ - ┌──────────────────────────────────┐ - │ C: fzf_native_score │ - │ - copy_emacs_string("foobar") │ - │ - fzf_parse_pattern("fb") │ - │ - fzf_get_score(text, pat, slab)│ - │ - free pattern │ - │ - build (score idx idx ...) cons│ - └──────────────────────────────────┘ + ┌──────────────────────────────────────────┐ + │ C: fzf_native_score │ + │ - copy_emacs_string("foobar") │ + │ - fzf_parse_pattern("fb") │ + │ - fzf_get_score(text, pat, slab) │ + │ - if fussy-fzf-native-highlight non-nil │ + │ and score > 0: │ + │ - fzf_get_positions │ + │ - put-text-property on args[0] with │ + │ face=completions-common-part │ + │ - free pattern │ + │ - return (score) │ + └──────────────────────────────────────────┘ │ ▼ - Elisp value: (score idx0 idx1 ...) + Elisp value: (score) ; match indices not surfaced #+end_src ** Notes @@ -154,6 +159,12 @@ the fzf-native backend. avoid that cost. - Non-UTF-8 Emacs strings are coerced via =copy_emacs_string=, falling back to =encode-coding-string= if =make_string= would signal. +- Highlighting is applied in C, not Elisp. The C module reads + =fussy-fzf-native-highlight= via =symbol-value= on every call; any + non-nil value enables highlighting (the cap concept used by + =fzf_native_score_all= does not apply to a single candidate). The + return value is =(score)= — match indices are no longer surfaced + to Elisp callers. * Path 2 — sync batch (=fzf_native_score_all=) From 6e409e8300b4a6b5f58b9786b345d95b9ac6440b Mon Sep 17 00:00:00 2001 From: James Nguyen Date: Sun, 10 May 2026 16:15:05 -0400 Subject: [PATCH 47/47] Update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 55e1ecf..298acbd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build /.eask/ /dist/ fzf-native.log +fzf-native-pkg.el