Skip to content

Remove repeated work from lowering and name resolution in long chains of bindings - #294

Merged
Rigidity merged 2 commits into
mainfrom
rigidity/linear-lowering
Nov 14, 2026
Merged

Rigidity merged 2 commits into
mainfrom
rigidity/linear-lowering

Conversation

@Rigidity

@Rigidity Rigidity commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Several compiler passes redid work for every binding or declaration in a chain, which made them quadratic or worse in its length and made a few tests much slower than they need to be. This removes that repeated work.

Changes

  • Lowering (rue-hir/src/lower.rs)
    • Dependency sets are now cached per symbol, so they aren't recomputed every time a symbol is lowered.
    • group_symbols is now a topological layering in a single pass, instead of repeatedly scanning for symbols whose dependencies are ready.
    • lower_let_stmts now drains the leading statements at once rather than calling remove(0) in a loop.
  • Recursion check (check_recursion.rs)
    • Cycles are found with one iterative Tarjan's SCC pass, instead of a separate search from each declaration.
    • A declaration that references a cycle without being part of it is still not reported. tests/symbols/invalid_recursion now covers that case.
    • One diagnostic changes: a declaration reached from a cycle more than once (const X = Y + Z; const Y = X; const Z = X;) is now reported once, where it used to be reported twice.
  • Path resolution (compile/path.rs)
    • In the current scope, only the kind of name that the path expects is looked up. The other kind is looked up only if that fails.
  • Symbol types (compiler.rs)
    • symbol_type_in walks the scope chain only for symbols whose type is overridden in some scope, which is tracked in push_mappings.
  • Unused export lint (exports.rs)
    • The worklist in used_exports now shares one visited set across all exports, so types reached from one signature aren't walked again from the next.
  • Tests
    • Added long_runs_of_bindings_are_grouped, which compiles 1000 chained bindings on the small stack (about 1.5s in debug).
    • Added tests/symbols/recursion_cycles: cycles of three or more, one cycle reaching another, mutually recursive inline functions, a constant that cycles through a function, and the deduplicated case above.
    • Added tests/symbols/symbol_and_type_names, which covers a type where a symbol is expected, a symbol where a type is expected, and a name that is both.

Timings

These are debug builds, compared against 65b2f24d. They were measured together with #295, but these tests only touch the code changed here:

Test Before After
chains_beyond_the_lowering_limit_are_an_error 12.1s 4.9s
deep_types_are_checked 10.2s 4.7s
long_chains_of_bindings_are_followed 6.5s 1.0s

With the debug CLI, a function containing 300 chained bindings went from 71s to 0.35s.

Not done here

- Test cycles of three or more declarations, one cycle reaching another, mutually recursive inline functions, a constant that cycles through a function, and a declaration reached from a cycle more than once
- Test names that are a symbol, a type, or both, in either position
- Drop the redundant `placed` vector from `group_symbols` and count the rounds once
- Keep each declaration's index in its visit instead of looking it up again
- Shorten the fallback lookup in path resolution
@Rigidity Rigidity changed the title Make lowering and name resolution linear in long chains of bindings Remove repeated work from lowering and name resolution in long chains of bindings Nov 14, 2026
@Rigidity
Rigidity merged commit 129926f into main Nov 14, 2026
3 checks passed
@Rigidity
Rigidity deleted the rigidity/linear-lowering branch November 14, 2026 23:15
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.

1 participant