Problem
The worker/wrapper uncurrying pass from #24 runs once, in the middle of the IR pipeline: after the post-merge optimize/dce fixpoint has settled the manifest arities, and before floatIn, magicDo and flattenDeepBinds. Two families of saturated n-ary call sites do not exist yet at that point, so the pass never splits the functions behind them.
Effect functions forced after magicDo
A curried effect action like f :: A -> B -> Effect C is, on the Lua target, a function that returns a thunk: applying it to its two real arguments yields \_ -> body, and running the effect forces that thunk with a final unit application. The saturated site is therefore f(a)(b)(), a three-argument spine. That third application only appears once magicDo has rewritten the do-block into explicit thunk-forcing. When the uncurry pass runs, magicDo has not run, so the site is still the partial f(a)(b) and f is left curried. The functions that would benefit most, the ones threaded through effectful loops, are exactly the ones the pass cannot see.
The $kont helpers minted by flattening
flattenDeepBinds lambda-lifts long continuation chains into named $kont helpers. Each helper takes the captured live set plus the bound value, so its arity is at least two and every call it introduces is saturated by construction. These helpers are created downstream of the uncurry pass, so their saturated sites are never offered to it either.
Approach
Run the same pass a second time, late, after flattenDeepBinds, followed by a short optimize/dce fixpoint to drop the wrappers and beta-reduce the use-once workers. The pass is idempotent and cheap, so a second run over already-uncurried code costs one traversal and changes nothing where there is nothing new to do. Moving the single early run later instead is not an option: it has to observe the arities that only settle after the early inlining fixpoints, so the early run must stay where it is.
Prerequisites / Relations
Builds on #24. Synergy with #181: a late run gives effectful loops their n-ary workers, which is the shape loopification needs to turn a tail-recursive effect loop into a Lua while.
Open questions
Problem
The worker/wrapper uncurrying pass from #24 runs once, in the middle of the IR pipeline: after the post-merge optimize/dce fixpoint has settled the manifest arities, and before floatIn, magicDo and flattenDeepBinds. Two families of saturated n-ary call sites do not exist yet at that point, so the pass never splits the functions behind them.
Effect functions forced after magicDo
A curried effect action like
f :: A -> B -> Effect Cis, on the Lua target, a function that returns a thunk: applying it to its two real arguments yields\_ -> body, and running the effect forces that thunk with a final unit application. The saturated site is thereforef(a)(b)(), a three-argument spine. That third application only appears once magicDo has rewritten the do-block into explicit thunk-forcing. When the uncurry pass runs, magicDo has not run, so the site is still the partialf(a)(b)andfis left curried. The functions that would benefit most, the ones threaded through effectful loops, are exactly the ones the pass cannot see.The
$konthelpers minted by flatteningflattenDeepBindslambda-lifts long continuation chains into named$konthelpers. Each helper takes the captured live set plus the bound value, so its arity is at least two and every call it introduces is saturated by construction. These helpers are created downstream of the uncurry pass, so their saturated sites are never offered to it either.Approach
Run the same pass a second time, late, after
flattenDeepBinds, followed by a short optimize/dce fixpoint to drop the wrappers and beta-reduce the use-once workers. The pass is idempotent and cheap, so a second run over already-uncurried code costs one traversal and changes nothing where there is nothing new to do. Moving the single early run later instead is not an option: it has to observe the arities that only settle after the early inlining fixpoints, so the early run must stay where it is.Prerequisites / Relations
Builds on #24. Synergy with #181: a late run gives effectful loops their n-ary workers, which is the shape loopification needs to turn a tail-recursive effect loop into a Lua
while.Open questions
Bench.CurriedStep) over an effectful loop and/or a long$kontchain would need to be picked to confirm the win.