Skip to content

SpecConstr: specialize recursive functions on known-constructor call patterns #208

Description

@Unisay

Problem

A recursive function that carries a constructor accumulator allocates the box on every iteration only to take it apart at the top of the next one:

loop :: Tuple Int Int -> Int
loop acc = case acc of
  Tuple s i
    | i >= n -> s
    | otherwise -> loop (Tuple (s + i) (i + 1))

Every iteration builds a fresh Tuple table and the next iteration's match immediately deconstructs it. Fold-shaped loops with Tuple/Maybe/Either accumulators are everyday PureScript, and the #177/#178/#180 cascades will produce more of these shapes as they collapse monadic chains into recursive functions.

Approach

GHC's SpecConstr (call-pattern specialisation): for a recursive binding whose body scrutinizes a parameter and whose recursive call sites pass a known constructor at that position, mint a specialized copy taking the constructor's fields as separate parameters, and rewrite those sites:

-- specialized worker: zero allocations per iteration
local loop_s = function(s, i)
  if i >= n then return s else return loop_s(s + i, i + 1) end
end
-- entry wrapper stays: a boxed accumulator can still arrive from outside
local loop = function(acc) return loop_s(acc.value0, acc.value1) end

The machinery is the #24 discipline reapplied (mint a copy, rewrite the qualifying sites, keep the wrapper for the rest), with beta plus the #177 fold cleaning up the specialized body. Guards learned from GHC:

Prerequisites / Relations

Hard prerequisite: #177, the specialized body's deconstruction folds away through it. Composes with the landed #24 split (specialization applies to n-ary workers) and with #181 (after loopification, per-iteration allocation is the main remaining cost in these loops). #206 is the result-side dual: together they pass raw components through a loop with no box materialized in either direction.

Parked: pick this up once #177/#178/#180 have landed and the #172 counters show constructor allocations inside hot recursions.

Verification / Measurement

An allocation counter on a Tuple-accumulator fold benchmark drops to zero tables per iteration, eval goldens stay byte-identical, and golden.lua sizes confirm the code-growth cap holds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    OptimisationA Compiler Optimisationarea: irIR / optimizer / DCE / inliner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions