Skip to content

fir: every function re-evaluates ALL top-level lets in its prelude (no reference analysis, no shared global storage) #48

Description

@brianp

Found during the documentation-validation sweep (Aug 2026).

Current behavior

fir/src/lower/stmt.rs:144-165 — "Inject globals into the function scope": when lowering each function, the lowerer builds a global_prelude of fresh FirStmt::Lets that re-evaluate every recorded top-level let (snapshot of top_level_lets), plus replays deferred top-level statements (top_level_stmts, stmt.rs:165). There is no analysis of which globals the function actually references.

Problems

  1. Semantics: a side-effecting top-level initializer runs once per function invocation, not once at program start. That is observably different from what a module-level let suggests.
  2. Performance: O(functions × globals) redundant evaluation, paid on every call.
  3. Shared state: since each function gets its own fresh locals, it appears module-level mutable state cannot exist (a reassignment in one function is invisible to another). Worth a test to confirm — if true, it should at least be a documented and diagnosed limitation rather than a silent one.

Possible directions

  • True global storage: runtime cells initialized once (the __top_main entry synthesis in the lowerer already exists as a natural home for one-time init).
  • Or, at minimum: reference analysis so functions only re-evaluate globals they use, plus documentation/tests locking in the once-per-function semantics deliberately.

Documentation

The observed behavior is documented in the docs site under docs/src/content/docs/internals/fir.mdx (globals injection section).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcodegenJIT, AOT, FIR lowering, runtimehighImportant, address soon

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions