Skip to content

Lower ST/Effect loop combinators (foreachE/forE/whileE) to native Lua for/while #233

Description

@Unisay

Problem

An ST/Effect loop stays a call into a foreign higher-order combinator. Effect.foreachE arr f, forE lo hi f, whileE cond body and their ST twins compile to a foreign function handed a Lua closure that it calls once per iteration: one closure allocation and one call per step, on top of the combinator's own call. magicDo flattens the surrounding bind-chain but never touches the loop: the body stays a lambda passed to a foreign iterator.

Lowered at codegen time they become native loops:

  • foreachE arr ffor i = 1, #arr do <body> end with f's parameter bound to arr[i]
  • forE lo hi ffor i = lo, hi - 1 do <body> end
  • whileE cond bodywhile cond() do <body> end

so both the per-iteration closure and the foreign call disappear. This is the largest untapped constant-factor gap, and it is exactly the imperative shape hot code uses (Data.Array.ST, VDom-style traversals).

A grep of lib/ confirms pslua has no lowering for any of these combinators today.

Approach

A codegen-time, name-keyed native-inline table matched to pslua's target. Match a saturated application of a known loop combinator by qualified name and spine shape, and emit the corresponding Lua statement instead of the call, inlining the body lambda as the loop body with its parameter as the loop variable:

  • ranged forE lo hi \i -> ...for i = lo, hi - 1 do ... end (PureScript forE is the half-open [lo, hi), so the numeric for runs to hi - 1);
  • foreachE arr \x -> ...for i = 1, #arr do ... end with x bound to arr[i], the shape the foreign implementation itself uses;
  • whileE \_ -> cond \_ -> bodywhile cond do ... end.

The menu is portable; the emission is Lua-specific. Fire only on the Effect/ST dictionaries (the gate magicDo already uses), never on a polymorphic foreachE whose dictionary is unresolved.

Prerequisites / Relations

Independent to land. Relates to the magicDo work: #182 (recognise Effect/ST by qualified name) and #228 (marker-driven do-chunks) both make the effect core easier to match structurally, and #182's own design record names "compiling forE/whileE/tailRecM over Effect into native loops" as a revisit trigger. This is that work. Composes with #239 (ref-cell unboxing): a native loop mutating an unboxed local is the end shape. Orthogonal to loopification (#181), which builds loops from tail recursion rather than from combinators.

Verification / Measurement

A foreachE/forE body compiles to a Lua for with no per-iteration closure and no foreign call in golden.lua, and a matching eval golden proves identical runtime output (iteration count, side-effect order). A LuaJIT FNEW-census drop on the loop body through the #172 harness. A non-Effect/ST use of the same combinator names is left as a call, the soundness guard, tested directly.

Metadata

Metadata

Assignees

Labels

OptimisationA Compiler Optimisationarea: codegenLua code generation / printingenhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions