Skip to content

Proposal for bend.ts: content-keyed template instance names, for parallel and cached checking #1633

Open

Description

@ScottMoore0

AGENTS.md keeps bend2/bend.ts human-written, so this is a proposal for the team, not a PR. The measurements are on main (e5527d5). A companion issue, #1632, covers error messages.

Idea. book_valid(book, done) already treats the first done entries as validated. So a worker can check a contiguous range [a, b) by loading the file, keeping book.order up to b and calling book_valid(book, a). The result of a parallel check is then the failure with the smallest entry index. Every entry before it passed, so its context is the sequential one, and its error is the one the sequential check reports. Workers past that index can be cancelled. Loading is cheap next to checking (13–112 ms against up to 3.7 s here). And since HTerms hold closures and can't cross workers, each worker reloading from source is the natural design. That part needs no change to bend.ts.

Measured (contiguous ranges, 8 workers simulated in one process, main e5527d5):

File Sequential Slowest of 8 Speed-up
bend-mathlib sort.bend 148 ms 79 ms 1.9×
typed/examples.bend 2,476 ms 1,816 ms 1.4×
dynamic_array/proof.bend 541 ms 424 ms 1.3×
aes/inverse.bend 3,719 ms 4,935 ms 0.8×

The gains are capped by two things:

  • single heavy defs (one def is 51–61% of inverse.bend and examples.bend);
  • each worker re-checking the template instances it calls.

def_inst names an instance by first use (tm.k + "~" + String(is.size)), so the same instance gets different names in different workers, and in different runs, and its check can't be shared.

Proposed bend.ts change: name an instance by its content, for example tm.k + "~" + <short hash of key> (key is already computed there). With deterministic names:

  • workers, and separate runs, can share an instance's checked result, which is what parallel checking needs to beat sequential on template-heavy files;
  • per-instance caching gets stable keys;
  • error messages and -o output stop depending on check order.

The cost is that instance names in messages and -o text become hash-based (e.g. keep~3f2a instead of keep~0).

My suggestion is to treat this as an enabler. Cross-file caching of whole import prefixes doesn't need it: #1631 does that today for --check-only. Per-instance caching, and parallel checking that beats sequential on template-heavy files, both do.

Found and measured with Claude Code, running Claude Opus 5.5, during an audit of --verdict.

Activity

  1. adeerkhan commented on Oct 18, 2026

    @adeerkhan
    Contributor

    Prototype: content-keyed template instance names (#1633)

    A local prototype on my fork to test the feasibility of #1633's idea. Not a
    PR
    — bend2/bend.ts is human-written per AGENTS.md, and both #1632/#1633 say
    they're proposals for the team. This branch just makes the idea concrete and
    measurable.

    Prototype branch (on my fork, no PR): https://github.com/adeerkhan/bend/tree/proto/1633-content-keyed-instances
    The diff is one function — hash_key + the two lines in def_inst.

    The change (one function)

    def_inst already computes a content key for each instance (the ~
    arguments, term_key'd and joined). It then names the instance by first-use
    order:

    // before (bend.ts, def_inst)
    o = tm.k + "~" + String(is.size);   // is.size = insertion order

    The prototype names it by a 64-bit FNV-1a hash of that same key instead:

    // after
    o = tm.k + "~" + hash_key(key);     // hash_key = FNV-1a 64-bit, base-36

    with a collision guard (an O(n) scan of the bucket) that in practice never
    fires at 64 bits.

    Measured

    Order-independence, on a template keep(~T: Type, x: T) used at U32 and
    Bool under two def orders:

    instance before (U32 first) before (Bool first) after (both orders)
    keep @ U32 keep~0 keep~1 keep~1ke1chrsw52qf
    keep @ Bool keep~1 keep~0 keep~1rvzz5wxctext
    • Before: the same U32 instance is ~0 or ~1 depending only on which def is
      checked first — so parallel workers (and separate runs) can't share it.
    • After: the name is a pure function of the arguments. Identical md5 of the
      checked book across repeated runs.
    • The C and JS lanes still emit, build and run correctly with the hashed names
      (keep~<base36> sanitizes exactly like today's keep~0).
    • Regression: full tests/**.bend --check-only sweep (1661 files), before vs
      after — identical: 588 pre-existing failures both ways, 0 new failures, 0
      changed.
      (Those 588 fail --check-only for reasons unrelated to naming —
      run-lane tests etc. — but the set is byte-identical before and after, so the
      change is a no-op on the suite.)

    Tradeoffs / notes for the team

    • Name length. base-36 64-bit ≈ 13–16 chars (keep~1ke1chrsw52qf) vs the
      issue's sketched keep~3f2a. Shorter is prettier but needs a collision
      check + a deterministic tiebreak to stay order-independent; 64-bit makes the
      guard dead code. Truncating to ~48 bits (~10 chars) at base36 would still be
      collision-safe for any real file.
    • It aligns with an existing convention. safe.ts already names its own
      elaborated constants content-keyed (k + "~" + F.k, safe.ts:337). This brings
      def_inst's instance names in line with that.
    • Where it pays off. With stable names, a checked instance can be keyed by
      its name for cross-worker / cross-run caching, and -o / error output stops
      depending on check order. That's the enabler both this issue and Proposals for bend.ts: clearer messages for seven common mistakes #1632 nod at:
      Proposals for bend.ts: clearer messages for seven common mistakes #1632's clearer messages become reproducible only if the names they quote are
      stable.

    Relationship to #1632

    Companion, not duplicate: #1632 is message wording, this is instance naming.
    The overlap is exactly the stability point above — content-keyed names make the
    instance names that appear in messages deterministic, which is a prerequisite
    for #1632's diagnostics (and any per-instance cache) to be stable. They remain
    two independent changes to bend.ts.

  2. ScottMoore0 commented on Oct 18, 2026

    @ScottMoore0
    ContributorAuthor

    Thank you for prototyping this, and for measuring it. It's good to see the names come out the same whichever def is checked first, the checked book identical across runs, and the test sweep unchanged.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions