Skip to content

Lift the *.Uncurried mk wrappers to n-ary function definitions #227

Description

@Unisay

#226 (issue #198) lifted the run half of the uncurried wrappers: a saturated runFn3 add3 1 2 3 now compiles to add3(1, 2, 3). The constructors stayed opaque FFI, and they undo much of the win. add3 = mkFn3 \a b c -> a + b + c still emits as mkFn3(function(a) return function(b) return function(c) ... end end end): the wrapper uncurries the call from the outside but re-curries on the inside via fn(a)(b)(c), so every call of a Fn3 built this way allocates two intermediate closures. The goal is add3 = function(a, b, c) return a + b + c end, with zero closures and the mk* accessors gone from the emitted FFI tables the same way the run* ones went in #226.

Foreign shapes

From the fork FFI (Data.Function.Uncurried, Control.Monad.ST.Uncurried, Effect.Uncurried):

  • mkFn2..mkFn10 are function(fn) return function(a, …, n) return fn(a)…(n) end end: a multi-parameter function literal wrapping a curried chain;
  • mkSTFn1..mkSTFn10 and mkEffectFn1..mkEffectFn10 are the same shape with a trailing force: return fn(a)…(n)();
  • mkFn0 wraps a nullary call fn() and stays opaque, symmetric with runFn0; mkFn1 and runFn1 have no foreign at all (they are PureScript-side id).

Approach

Extend the foreign lifter (#178) with two shapes, mirroring how #198 handled the run half, and allowlist mkFn2..mkFn10, mkSTFn1..mkSTFn10, mkEffectFn1..mkEffectFn10:

  • A multi-parameter function literal (all parameters named; a vararg declines) lifts to a single n-ary AbsN, not to nested unary Abs. The nested encoding is exactly the misapplication the lifter's current decline comment warns about. The n-ary AbsN codegen already exists (the Program transformation to uncurry functions for which all applications are fully saturated. #24 uncurrying workers emit through it), and the WellApplied linter invariant only constrains literal-lambda heads of AppN, which this translation does not create.
  • A nullary call fn() in an FFI body is the run of an effect thunk. It lifts to an application of EffectRunArg, so isEffectRun recognises it throughout the IR pipeline and codegen erases the marker back to a plain fn(), the same treatment magic-do's own effect runs get.

With both in place, mkFn3 lifts to \fn -> AbsN [a, b, c] (fn(a)(b)(c)), and a mkFn3 \a b c -> … site beta-reduces layer by layer to a bare n-ary AbsN, i.e. a direct Lua function(a, b, c) definition. Compositions with the run half reduce end to end: runFn3 (mkFn3 f) x y z collapses to f x y z, and mkEffectFn2 (runEffectFn2 h) either reduces to a direct AppN h [a, b] (the marker lands on the thunk's unused parameter and is dropped) or is left as the effect-run redex that the codegen fusion from #226 already collapses.

One guardrail belongs in the allowlist comment: after this change the Effect core (bindE, pureE) becomes technically liftable (their bodies are pure return trees), but it must not be allowlisted. Magic-do recognises bind chains by name, and inline-always lifting of bindE in the optimizer fixpoint (which runs before magic-do) would blind that recognition, which would cost the flat do-chunks and with them the handling of Lua's nesting and locals limits (#19). The broader-allowlist follow-up (#187) needs that warning spelled out.

Future direction (#228)

The cleaner endgame is marker-driven magic-do: normalise every effect run to EffectRunArg before magic-do runs, and build do-chunks from those semantics instead of recognising bindE/pureE by name. That would remove the name-driven fragility the guardrail above works around, but it means redesigning magic-do's recognition and the way chunk boundaries keep generated functions within Lua's limits (#19), so it is tracked separately in #228.

Metadata

Metadata

Assignees

No one assigned

    Labels

    OptimisationA Compiler Optimisationarea: irIR / optimizer / DCE / inlinerenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions