fix: PSLUA_runtime_lazy did not memoize (state reset on every force)#126
Merged
Conversation
The runtime-lazy fixture declared `local state`/`local val` inside the forcing thunk, so a fresh pair was created on every force and the memoizing state machine never advanced. As a result a lazy recursive binding re-ran its initializer on every reference, returned a fresh value each time (breaking reference identity), and looped to a stack overflow instead of reporting the "needed before it finished initializing" error on an ill-founded cycle. Move the two locals up into the enclosing `function(init)` closure so they become upvalues of the returned thunk and persist across forces; the initializer now runs at most once. Add a Lua.Run test that forces a counted initializer twice and asserts it ran once (red before, green after). The 13 affected golden.lua files change only by the two-line move; golden.ir is untouched. Found while reading CoreFn.Laziness for #44.
There was a problem hiding this comment.
Pull request overview
This PR fixes a correctness bug in the Lua runtime fixture PSLUA_runtime_lazy used by the laziness transform fallback path. Previously, the fixture reinitialized its memoization state on every force; this change makes state/val persist across forces so the initializer runs at most once and cyclic initialization is detected reliably.
Changes:
- Fix
PSLUA_runtime_lazymemoization by hoistingstate/valinto thefunction(init)closure inBackend/Lua/Fixture.hs. - Add a
Lua.Runregression test intended to exercise memoization by forcing twice and checking the initializer ran once. - Re-accept affected golden Lua outputs and add a changelog fragment describing the fix.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| lib/Language/PureScript/Backend/Lua/Fixture.hs | Hoists state/val so runtime lazy values properly memoize and preserve initialization state across forces. |
| test/Language/PureScript/Backend/Lua/Run/Spec.hs | Adds a regression test for PSLUA_runtime_lazy memoization (currently contains a Lua syntax issue; see PR comment). |
| changelog.d/20260625_185827_unisay_runtime_lazy_memoization.md | Documents the user-visible behavior fix and the new regression test. |
| test/ps/output/Golden.TailRecM2Shadow.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.StringCodePoints.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.ProfunctorDictLens.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.MaybeChain.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.LongDoBlock.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.Issue37.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.HelloPrelude.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.GenericEqTwoTypes.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.DerivedFunctor.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.CharLiterals.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.BugListGenericEq.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.ArrayPatternMatch.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
| test/ps/output/Golden.ArrayOfUnits.Test/golden.lua | Re-accepted golden Lua output reflecting the fixture change. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
PSLUA_runtime_lazyruntime fixture declared itsstateandvallocals inside the forcing thunk (return function() local state = 0 ... end) rather than in the enclosingfunction(init)closure. Because a freshstate/valwas created on every call to the thunk, the memoizing state machine never advanced past its initial state. All three of the fixture's jobs were dead code:The fixture is emitted whenever the laziness transform falls back to a runtime lazy initializer, which happens for recursive binding groups it cannot statically order. It shows up in 13 of the golden outputs (typeclass-dictionary recursion), so real programs hit it.
The fix moves the two
localdeclarations up one scope so they become upvalues of the returned thunk and persist across forces. The initializer now runs at most once.I found this while reading
CoreFn.Lazinessto document it for #44; it is unrelated to the documentation work, so it ships on its own.How it was verified
A new
Lua.Runtest runs the actual fixture throughlua: it builds a lazy value whose initializer bumps a counter, forces it twice, and exits 0 only if the counter is 1. The test was red before the fix (exit 1, initializer ran twice) and is green after. The 13 affectedgolden.luafiles change only by the two-line move;golden.iris untouched (the fixture is a codegen-level artifact).Checklist
changelog.d/fragment for any user-facing change (scriv createin the dev shell), or this change ships nothing releasable (CI, docs, or an
internal refactor).
nix develop),fourmolu -i lib/ exe/ test/andhlint lib/ exe/ test/are clean.cabal test allpasses; structural goldens werere-accepted on purpose because codegen moved (
PSLUA_GOLDEN_ACCEPT=1), andeval/golden.txtstill holds.