fix: retain sparse anchors for long ascii prefixes

Co-authored-by: mmamedel <23098414+mmamedel@users.noreply.github.com>
This commit is contained in:
Chris Tate
2026-08-31 14:47:05 -05:00
co-authored by mmamedel
parent 0392d0bd4d
commit faeb8f1043
2 changed files with 50 additions and 0 deletions
+17
View File
@@ -555,6 +555,21 @@ static void scr_sidx_extend_to_u16(const ScrStr *s, ScrSidx *e, size_t u16) {
scr_sidx_finish(s, e);
}
/* A large, not-yet-complete string can have a long proven-ASCII prefix
* before its first non-ASCII byte. That prefix is an exact identity map, but
* merely advancing indexed_{cu,cb} through it leaves alternating on-demand
* lookups with only the hot cursor and therefore linear backtracks. Once an
* indexed conversion reaches such a prefix, retain its arithmetic stride
* anchors too. A later full scan still drops them if the whole string proves
* ASCII, so the all-ASCII steady state remains the allocation-free identity
* fast path. */
static void scr_sidx_materialize_identity_prefix(const ScrStr *s, ScrSidx *e) {
if (e->npoints == 0 && e->indexed_cb != 0 &&
e->indexed_cb == e->indexed_cu) {
(void)scr_sidx_prepare_points(s, e);
}
}
/* Cached UTF-16 length. Large strings extend from their exact previously
* indexed prefix, retaining sparse start/end anchors; short strings keep the
* historical single word-wise scan. `u16len == byte len` proves ASCII and
@@ -660,6 +675,7 @@ static size_t scr_u16_to_byte_c(const ScrStr *s, ScrSidx *e, size_t u16,
return u16 < s->len ? u16 : s->len;
}
scr_sidx_extend_to_u16(s, e, u16);
scr_sidx_materialize_identity_prefix(s, e);
ScrSidxPoint near = scr_sidx_near_u16(s, e, u16);
size_t cu = near.cu, cb = near.cb;
while (cu > u16) scr_sidx_back(s, &cu, &cb);
@@ -692,6 +708,7 @@ static size_t scr_byte_to_u16_c(const ScrStr *s, ScrSidx *e,
while (e->indexed_cb < byte_off) scr_sidx_extend_one(s, e);
scr_sidx_finish(s, e);
}
scr_sidx_materialize_identity_prefix(s, e);
ScrSidxPoint near = scr_sidx_near_byte(s, e, byte_off);
size_t cu = near.cu, cb = near.cb;
while (cb > byte_off) scr_sidx_back(s, &cu, &cb);
+33
View File
@@ -341,6 +341,38 @@ static void sparse_index_asserts(void) {
scr_str_release(s);
scr_sidx_test_reset_cache();
}
/* Do not wait for the first non-ASCII byte before proving this access shape.
* A string can have megabytes of ordinary ASCII followed by one emoji: no
* `.length` prime is involved here, and alternating distant reads in that
* prefix must retain identity checkpoints instead of repeatedly walking the
* distance between the two hot-cursor positions. */
static void sparse_ascii_prefix_asserts(void) {
enum { PREFIX = 4 * 1024 * 1024, QUERIES = 8 };
const size_t first = (size_t)1024 * 1024 + 137;
const size_t second = (size_t)3 * 1024 * 1024 + 271;
char *raw = malloc((size_t)PREFIX + 4);
if (!raw) { sidx_fail("ASCII-prefix test allocation"); return; }
memset(raw, 'a', PREFIX);
memcpy(raw + PREFIX, "\xF0\x9F\x98\x80", 4); /* terminal emoji */
ScrStr *s = scr_str_new(raw, (size_t)PREFIX + 4);
free(raw);
scr_sidx_test_reset_cache();
scr_sidx_test_reset_steps();
for (size_t q = 0; q < QUERIES; q++) {
size_t at = q & 1 ? second : first;
if (scr_str_char_code_at(s, (double)at) != 97.0)
sidx_fail("ASCII-prefix charCodeAt result");
}
if (scr_sidx_test_points() == 0)
sidx_fail("ASCII-prefix identity checkpoints were not retained");
if (scr_sidx_test_walk_steps() > (size_t)QUERIES * 4200)
sidx_fail("ASCII-prefix lookup exceeded sparse stride bound");
scr_str_release(s);
scr_sidx_test_reset_cache();
}
#endif
int main(int argc, char **argv) {
@@ -507,6 +539,7 @@ int main(int argc, char **argv) {
accumulation_asserts();
#ifdef SCR_SIDX_TEST
sparse_index_asserts();
sparse_ascii_prefix_asserts();
#endif
#ifdef SCR_RC_AUDIT