Problem
pslua compiles runFnN/runSTFnN/runEffectFnN to their runtime fallback onions. The forks already declare uncurried FFI throughout (zipWithImpl is Fn3, pushImpl is STFn2, indexImpl is Fn4), but a saturated runSTFn2(pushImpl)(x)(arr)() still pays two closure allocations plus a thunk per call, exactly what curried FFI costs. On the Lua target the ecosystem's uncurried discipline currently buys nothing.
Approach
Lift the *.Uncurried wrappers through the allowlist-driven foreign lifter (#178) into the n-ary AppN node (#179). runFn3 lifts to \fn a b c -> AppN fn [a, b, c]. The effect variants are expressible with today's nodes: runSTFn2 lifts to \fn a b -> Abs _ (AppN fn [a, b]), where the thunk is a unary lambda with an unused parameter. The lifted wrappers are tiny and marked inline-always, so a saturated site reduces to a direct AppN impl [x, y, z] after beta, while a partial application keeps the wrapper and its fallback semantics.
magicDo fusion then follows for free: the lifted thunk Abs _ (AppN ...) is the shape magicDo already executes in statement position, so local _ = M.Data_Array_ST_push(x)(result)() becomes pushImpl(x, result), with zero closure allocations and one call instead of four.
Prerequisites
This is the perf-bearing half of the original #179. The node landed in that issue; the lifting waits on #178.
Verification / Measurement
The win is the removal of per-call closure allocations on the FFI boundary: the whole ST/Array traffic (push, poke, index, zipWith, thaw). Curried application overhead is about 4.3x on PUC and 54x on LuaJIT today. #172 should grow a dedicated STFn-call before/after microbenchmark to track it. The measured 85x on the foldl driver combines this with PureScript-side currying of the step function; that second half belongs to #24.
Problem
psluacompilesrunFnN/runSTFnN/runEffectFnNto their runtime fallback onions. The forks already declare uncurried FFI throughout (zipWithImplisFn3,pushImplisSTFn2,indexImplisFn4), but a saturatedrunSTFn2(pushImpl)(x)(arr)()still pays two closure allocations plus a thunk per call, exactly what curried FFI costs. On the Lua target the ecosystem's uncurried discipline currently buys nothing.Approach
Lift the
*.Uncurriedwrappers through the allowlist-driven foreign lifter (#178) into the n-aryAppNnode (#179).runFn3lifts to\fn a b c -> AppN fn [a, b, c]. The effect variants are expressible with today's nodes:runSTFn2lifts to\fn a b -> Abs _ (AppN fn [a, b]), where the thunk is a unary lambda with an unused parameter. The lifted wrappers are tiny and marked inline-always, so a saturated site reduces to a directAppN impl [x, y, z]after beta, while a partial application keeps the wrapper and its fallback semantics.magicDo fusion then follows for free: the lifted thunk
Abs _ (AppN ...)is the shape magicDo already executes in statement position, solocal _ = M.Data_Array_ST_push(x)(result)()becomespushImpl(x, result), with zero closure allocations and one call instead of four.Prerequisites
*.Uncurriedallowlist is explicitly follow-up work there.AppNcall node and theWellAppliedlinter invariant that guards the lifted shapes.This is the perf-bearing half of the original #179. The node landed in that issue; the lifting waits on #178.
Verification / Measurement
The win is the removal of per-call closure allocations on the FFI boundary: the whole ST/Array traffic (
push,poke,index,zipWith,thaw). Curried application overhead is about 4.3x on PUC and 54x on LuaJIT today. #172 should grow a dedicated STFn-call before/after microbenchmark to track it. The measured 85x on the foldl driver combines this with PureScript-side currying of the step function; that second half belongs to #24.