Skip to content

interp: building the text of every error is quadratic in program size #5833

Closed

Description

@0pcom

errorAt in interp/errors.go calls llvm.Value.String() on the failing instruction for every error it creates, once for Inst and once for the traceback. Printing a value makes LLVM number the whole module first, so each call costs time in proportion to the module size.

Most of these errors are never shown. They are the normal "leave this instruction for runtime" path, and the interpreter catches them and moves on. A large program makes thousands of them, so the cost grows with the square of the program size.

Building skycoin (github.com/skycoin/skycoin, 356 packages, -opt=z -no-debug -tags "safe netgo noasm") on linux/amd64 with LLVM 23 takes 341 s on dev with #5782 merged (for LLVM 23) and 303 s with the change below, with no change in peak memory. On a larger program (426 packages) the printing was 93 of 191 CPU seconds in a CPU profile.

The fix I have keeps the llvm.Value in the error and only builds the strings in a Render method that the diagnostics printer calls before it shows an error: 0magnet@ef96070

I can open a PR for it. Related to #5657.

Activity

  1. dgryski commented on Oct 6, 2026

    @dgryski
    Member

    I feel the Render() logic should be part of Error() instead. Maybe cache the resulting string if you're concerned about multiple calls.

  2. added 2 commits that reference this issue on Oct 8, 2026
    b0b40a1
    5cbfe1f
  3. 0pcom commented on Oct 14, 2026

    @0pcom
    ContributorAuthor

    I tried the lazy version and it has a lifetime problem. The text comes from the LLVM module, and by the time anything calls Error() or prints diagnostics the module can already be disposed. The first version (the commit linked above, rendering from the diagnostics printer) was a use-after-free for that reason. Building it in Error() and caching it would have the same problem. diagnostics also reads Inst and Traceback directly, without calling Error().

    The branch now has no exported method. Errors keep the llvm.Value, and Run and RunFunc fill in the text in a deferred call just before they return an error, while the module is still alive. The thousands of errors the interpreter catches internally are never printed. 0magnet@b0b40a12

    Would that work for you?

  4. 0pcom commented on Oct 21, 2026

    @0pcom
    ContributorAuthor

    Fixed by #5935. Thanks @joeshaw.

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