Skip to content

String.order walks both strings once and hands nothing back: a comparison is one tail call per char, 8x on the C lane, and String.eq of two 50000-char strings no longer overflows the JS lane - #1459

Merged
Lorenzobattistela merged 2 commits into
bendlang:mainfrom
aldeni:string-order
Oct 14, 2026

Conversation

@aldeni

@aldeni aldeni commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

String.order used to be Pair.snd(String.cmp(a, b)). That walk wasn't a tail call and rebuilt both strings, so the JS and interpreter lanes overflowed the stack on long strings. String.eq crashed on two 50,000-char strings.

The new String.order.go(a, b, c) walks both strings once and carries the order so far in c. It returns at the first differing char, or by length when one string is a prefix of the other. String.cmp and the Map ops that use it are unchanged.

  • Matches Pair.snd(String.cmp) on all 116,281 pairs of strings up to length 4 (empty strings, prefixes, chars 0 and 0xFFFFFFFF).
  • tests/base/string_order.bend covers the edge cases. Two of its cases check that the first difference beats later chars and lengths. It also runs 50,000-char strings, which crash main on interp, bun and node.
  • Mac: C compares 8-19x faster, sorting 100k keys 7.7x faster, JS 1.3-1.9x faster.
  • Gates on main d328354: repo 54/54, test 1626/1626, perf 124/124, ping 58/58, safe 1637 files with no false rejects, --verdict ALL PROOFS CHECK.

…ison is one tail call per char, 8x on the C lane, and String.eq of two 50000-char strings no longer overflows the JS lane

String.order was Pair.snd(String.cmp(a, b)): String.cmp hands both
strings back, so String.eq and the is_ twins rebuilt every char they
read and recursed through String.cmp.fin on the host stack of the JS
lane. Now String.order.go(a, b, c) carries the last chars' comparison
as a Cmp column after the strings, as Map.has.go carries its test, and
each char is one U32.cmp and one tail call. String.cmp stays for the
callers that keep the strings (Map's leaves).

Measured on Linux x86-64, one thread, 300,000 fresh ~20-char pairs
differing at the last char, net of building them: a C String.eq goes
from 1.10 us to 0.13 us; List.sort of 20,000 strings by String.is_le
from 61 ms to 17 ms; the JS lane 1.5x net. tests/base/string_order.bend
pins the order on empty, equal, prefix, first-char and last-char
differences and on two 50000-char strings, which on main fail the
interpreted and JS lanes with "memory fault (machine stack overflow?)".
A reflexivity lemma over String.eq is an induction on the string with
U32.cmp's reflexivity, and passes --verdict, as does the test. Repo gate
54/54; tests/base and tests/show unchanged against main on four lanes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Lorenzobattistela Lorenzobattistela self-assigned this Oct 14, 2026
@Lorenzobattistela
Lorenzobattistela merged commit 15a336c into bendlang:main Oct 14, 2026
1 check passed
@Lorenzobattistela Lorenzobattistela mentioned this pull request Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants