Skip to content

fix: PSLUA_runtime_lazy did not memoize (state reset on every force)#126

Merged
Unisay merged 2 commits into
mainfrom
issue-fix/runtime-lazy-memoization
Jun 25, 2026
Merged

fix: PSLUA_runtime_lazy did not memoize (state reset on every force)#126
Unisay merged 2 commits into
mainfrom
issue-fix/runtime-lazy-memoization

Conversation

@Unisay

@Unisay Unisay commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

The PSLUA_runtime_lazy runtime fixture declared its state and val locals inside the forcing thunk (return function() local state = 0 ... end) rather than in the enclosing function(init) closure. Because a fresh state/val was 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 initializer re-ran on every reference to the lazy binding, instead of once.
  • Each force returned a fresh value, so reference identity was not preserved (a correctness hazard for identity- or effect-bearing lazy values).
  • An ill-founded cycle (a binding that forces itself during initialization) looped until the stack overflowed instead of raising the intended "X was needed before it finished initializing" error.

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 local declarations 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.Laziness to document it for #44; it is unrelated to the documentation work, so it ships on its own.

How it was verified

A new Lua.Run test runs the actual fixture through lua: 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 affected golden.lua files change only by the two-line move; golden.ir is untouched (the fixture is a codegen-level artifact).

Checklist

  • Added a changelog.d/ fragment for any user-facing change (scriv create
    in the dev shell), or this change ships nothing releasable (CI, docs, or an
    internal refactor).
  • In the dev shell (nix develop), fourmolu -i lib/ exe/ test/ and
    hlint lib/ exe/ test/ are clean.
  • In the dev shell, cabal test all passes; structural goldens were
    re-accepted on purpose because codegen moved (PSLUA_GOLDEN_ACCEPT=1), and
    eval/golden.txt still holds.

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_lazy memoization by hoisting state/val into the function(init) closure in Backend/Lua/Fixture.hs.
  • Add a Lua.Run regression 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.

Comment thread test/Language/PureScript/Backend/Lua/Run/Spec.hs Outdated
@Unisay Unisay self-assigned this Jun 25, 2026
@Unisay Unisay marked this pull request as ready for review June 25, 2026 17:10
@Unisay Unisay merged commit 4795ab1 into main Jun 25, 2026
2 checks passed
@Unisay Unisay deleted the issue-fix/runtime-lazy-memoization branch June 25, 2026 17:10
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.

2 participants