feat(optimizer): collapse a tail-position IIFE into the enclosing function body (#230) - #293
Merged
Merged
Conversation
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.
Closes #230.
Problem
#227 left one residual closure on the mk half: an effectful uncurried definition whose body is a statement sequence runs its magic-do chunk through a tail IIFE. From
Golden.UncurriedLift.Test:The IR cannot fix this (beta reduction refuses effect-run redexes by design to protect magic-do's chunking, and the #226 codegen fusion covers only single-
AppNthunk bodies), so this lands as a Lua-level rewrite.The rule
collapseTailScopeCallinLanguage.PureScript.Backend.Lua.Optimizer: when a function body's last statement isreturn (function() <stmts> end)(), splice<stmts>in place of thereturn:Tail position is what makes the splice safe. The parent's
return callforwarded all of the call's results (an explicitParenwould adjust them to one and correctly fails the match), so after the splice the innerreturns — or falling off the end, for zero results — produce the same values directly. Early returns among the leading statements exit the parent on their own paths either way, so unlikefoldFieldProjectionThroughScopeCallthe rule needs no early-return decline. The issue's name-collision condition is discharged by construction: the splice point is the parent's last statement, so no parent code follows it, and Lua's positional local scoping keeps every spliced reference resolving exactly as it did inside the closure — a re-declaration of a name the parent already binds is legal and shadows only from that point on.Two conditions are checked. The spliced statements must not mention
...in their own scope — a no-parameter function cannot legally do so, but on such (only ever hand-written) input the splice would rebind...to the parent's varargs instead of failing to load; the check reuseschunkScopeUsesVararg, now exported fromLua.Linker.Foreign. And a local budget (#19): the splice undoes exactly the chunking with which magic-do bounds any single function's locals, so the merged body (parameters plusactivationLocalSlots, a proto-boundary-respecting over-approximation) must fit the sameworkingLocalCeilingthe storage passes budget against.LuaLimitsis threaded through the rewrite chain for this, which is whyfoldFieldProjectionThroughScopeCallandoptimizeExpressionnow take the parameter.Applied bottom-up by the existing driver, nested chunk chains collapse as far as the budget allows and no further: in
Golden.LongDoBlock.Testthe two adjacent magic-do chunks (150 + 149 locals against a ceiling of 180) keep their boundary, so that golden is unchanged.Golden churn
Exactly one golden moves —
Golden.UncurriedLift.Test/golden.lua, the diff shown above. Every other tail IIFE in the corpus is a correct decline: the remaining shapes are result-consuming double calls where the callee is not a function literal, e.g. fromGolden.LongApplyChain.Test:Eval goldens and
.irgoldens are untouched.Verification
Optimizer/Spec.hs: the splice, bottom-up cascading, splicing past a leading early return, zero-value fall-off, and declines for parameterized callees, budget overflow (asserted on both sides of a tiny customLuaLimits), and activation-scope varargs (with the nested-proto non-decline counterpart).cabal test allpasses clean twice (fresh Hedgehog seeds), 1026 examples, 0 failures;fourmoluandhlintclean.