Skip to content

index (U007) and concat (U004): see a List.get / append that the loop reaches through a helper or a continuation #198

Description

@ngngardner

Found while profiling bolt lint (branch autoresearch/bolt-lint-perf). Both rules only look at the recursive def's own body, so the same quadratic hides one call away.

index: List.get behind a non-recursive helper

syntax/bind.bend (before the fix):

def line_text(lines: List<&2, String>, nn: U32) -> String:
  String.from_list(lstrip(String.to_list(Maybe.default(&2, String, List.get(&2, String, lines, U32.to_nat(nn)), ""))))

def annotate(binds: List<&2, Bind>, +lines: List<&2, String>) -> List<&2, Bind>:
  match binds:
    case Con{Bind{name, +line, col, +kind, note}, rest}:
      Bind{.., note_for(kind, note, lines, line)} <> annotate(rest, lines)   # note_for -> line_text -> List.get

That's one List.get from line 0 per binder: 22k binders over 30k lines took ~20 s on bolt/rules/PROOF.bend. A cursor in source order fixed it (binder 35 s to 15.6 s). U007 didn't fire. It did fire on my first version of the fix, where the fallback List.get sat inline in the recursive def. Moving that fallback into a helper silences U007 the same way the original code did.

concat: append onto an accumulator through a continuation

bolt/lsp/files/disk.bend:

def slurp.cut(bytes, file, acc, rest: File -> List<&2, U32> -> IO(..)) -> IO(..):
  match bytes:
    case Con{b, t}:
      rest(file, List.append(&2, U32, acc, b <> t))     # rest is `fl => a => slurp(f, fl, a)`

Each 64 KB chunk appends onto everything read so far, which is quadratic in chunks. It's small in bolt (~0.2 s over the tree), but it's the textbook concat shape, and it's invisible because the loop goes slurp -> slurp.next -> slurp.cut -> rest(..).

Proposal

  • index: also report a List.get/String.get with a non-literal index in a non-recursive def of the file that a recursive def calls on its recursive path. Name the helper and the loop in the message.
  • concat: treat a call to a parameter that the loop passes as x => .. => self(..) like a self-call. Or more simply: an argument List.append(.., acc, ..) where acc is a parameter, passed to a function-typed parameter.

Activity

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions