Skip to content

Emission-time renumbering of compiler-minted locals: keep supply history out of artifacts #306

Description

@Unisay

Problem

Compiler-minted local names leak pipeline history into the emitted Lua text. Fresh names are drawn as x$N (rendered x_S_N by the Lua name mangler) from one monotone supply threaded through the whole IR pipeline — the uniquify pass, freshening on inline pastes, CSE, uncurrying all consume it — so any change that shifts the count of names consumed early in a module renames every later binder by a constant offset, even in code the change never touched. PR #304 showed the cost at review time: of the 26 golden modules that moved, several changed in nothing but indices (e.g. Golden.NativeLoopsGuard's v_S_1437 → v_S_1440 — the modules merely import unsafe-coerce transitively, and the newly lifted λx. x binder consumes supply indices where the binder-free accessor consumed none). This alpha-only churn is pure noise in golden diffs, inflates every optimizer PR's review surface, and forces PSLUA_GOLDEN_ACCEPT runs for changes that are semantically identity.

Approach

Normalize at emission, not in the IR: the supply must stay global — the pipeline's unique-binders invariant (GUC) depends on it — so quarantine the fix in a late Chunk → Chunk pass that runs after every name-minting Lua-level transform (localization/promotion, loopification) and immediately before the printer. The pass walks the chunk and renumbers every $N-suffixed local binder in first-occurrence order, rewriting its references; unsuffixed names are untouched. The domain is well-defined because PureScript identifiers cannot contain $, so a $-suffix marks a compiler-minted name exclusively; sequential renumbering preserves uniqueness, so the pre-existing no-capture property carries over unchanged. Emitted names thereby become a function of the artifact's own structure — byte-stable under any upstream change that only perturbs supply consumption. Nothing semantic reads these names: FFI tables key exports by export name, the runtime-lazy factory is keyed by a string literal, and the locals/upvalue budget counting is name-agnostic. Landing the pass causes a one-time whole-corpus golden renumbering, accepted once; a possible follow-up (out of scope here) is dropping the suffix entirely where the base name is unambiguous in scope, which needs real shadowing analysis rather than renumbering.

Prerequisites / Relations

Independent. Sibling of the emission-ergonomics issues #276 (no width bound on emitted lines) and #273 (lua-language-server diagnostics on call sites); reduces the golden-churn class that #303 discusses from the harness side.

Verification / Measurement

The red is demonstrable as a differential: burn a synthetic offset of supply indices at pipeline start (a test-only knob or a prepended dummy binder) and recompile the golden corpus — today every module's golden.lua churns in indices; with the pass, all artifacts are byte-identical under the perturbation. Plus: the pass is idempotent (normalizing twice equals once), luacheck stays clean, and every hand-verified eval oracle is unchanged — renumbering is alpha-renaming by construction, and the eval goldens prove it on the corpus.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: codegenLua code generation / printingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions