feat(optimizer): splice a tail-position application of a function literal - #296
Merged
Conversation
…eral (#295) Generalize collapseTailScopeCall to an applied function literal with named parameters: `return (function(p1, …) <stmts> end)(a1, …)` in tail position splices as `local p1, … = a1, …; <stmts>`, the simultaneous local reproducing Lua's call binding exactly. The nullary shape (#230) degenerates to no binding statement. Renamed to collapseTailLiteralApplication accordingly.
The pushed application may land on a returned function literal — a beta-redex built at a depth the bottom-up driver has already passed. A literal in a function tail re-collapses when the driver reaches the enclosing function, but one in expression position (a table row, an operand) had no later chance. foldCallThroughScopeCall now hands each literal it rebuilds to collapseTailLiteralApplication directly.
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 #295.
What
Generalizes the tail-position IIFE collapse (#230) to an applied function literal with named parameters, and renames the rule accordingly:
collapseTailScopeCall→collapseTailLiteralApplication. When a function body ends inreturn (function(p1, …) <stmts> end)(a1, …), the rule now binds the parameters as one simultaneouslocaland splices the body; the nullary shape degenerates to no binding statement, so #230's behaviour is the special case. OnGolden.LongApplyChain.Test'sapplySecond— the shape PR #294 deliberately left behind, one closure allocation and one extra call per node of the 40-deep apply chain:becomes straight-line:
Why the
localis exactThe one-statement
local p1, … = a1, …is the literal translation of Lua's call binding: the whole initializer list evaluates before any name binds (an argument reading an outerxcan never see a parameter namedx), a multi-valued expression last in the list expands, missing values fill withnil, and extra values evaluate and drop — the same adjustment rules the call performed. Unlike #294's fold, the arguments cross no scope boundary: the call site already was the parent's last statement, so they evaluate in the same environment at the same program point — which is also why...among the arguments needs no check here.Conditions on top of #230's (vararg check on the spliced statements, the #19 local budget — now counting the parameter binding — and the self-re-application): every parameter is
ParamNamedwith no name repeating, and a nullary literal applied to arguments declines (nolocalbinds zero names, and dropping the arguments would drop their effects).The second commit: re-collapse in expression position
The first commit alone moved only 3 goldens;
Golden.Issue37.TestandGolden.StringCodePoints.Test— both named in #295 — did not budge. The reason: their redexes sit in scope calls in expression position (a table row, an==operand), and the tailreturn (function(fn1) … end)(f)inside those scope calls is built byfoldCallThroughScopeCallitself at a depth the bottom-up driver has already passed — the callee literal is visited before the enclosing call node, so nobody revisits the rebuilt literal. A literal in a function tail recovers when the driver reaches the enclosing function (collapseTailLiteralApplicationfires there and re-applies); a literal in expression position had no later chance.The fold now hands each literal it rebuilds to
collapseTailLiteralApplicationdirectly — the same eager re-optimizationfoldFieldProjectionThroughScopeCallalready does with its projected return, and the reason the fold now takesLuaLimits. InGolden.Issue37.Test:becomes
In
Golden.LongWriterBind.Testthe chain composes end-to-end — fold, collapse, projection fold, table-constructor fold — reducing((function(dictApplicative_S_518) return { pure = …, Apply0 = … } end)(Data_Identity_applicativeIdentity)).pure(42)toThe folded-away
Apply0row held the only read ofControl_Monad_Writer_Trans_applyWriterT_S_w(2 occurrences on main → 1 now: just the initializer), sopromoteChunkcorrectly declines to promote a zero-read binding — that is whylocal M = {}reappears in this golden with the now-dead initializer parked asM.Control_Monad_Writer_Trans_applyWriterT_S_w = …. One golden also lost a nesting level that main's own output would have collapsed if re-fed to the optimizer (Golden.ArrayOfUnits.Test's main): the nesting was fold residue at a passed depth, the class this commit removes.Verification
local x, y = 1, the bare-rule re-collapse through a parameterized merge, declines (nullary-with-arguments, vararg/unused/duplicate parameters, budget counting the bound parameters), and the fold's re-collapse in expression position. One existing fold expectation updated: the nested-scope-call re-fold now flattens to a single scope call (strictly better output, same purpose).golden.luamoved across the two commits (LongApplyChain,BugListGenericEq,ArrayOfUnits,Issue37,StringCodePoints,LongWriterBind);golden.iruntouched. All eval goldens byte-identical — the semantic oracle is never auto-accepted.bench/ci --acceptproduced no diffs: the bench modules carried none of the residual shapes after feat(optimizer): fold a trailing call through a scope call #294.cabal test allclean (1044 examples),fourmolu+hlintclean.