Problem
Monadic code outside Effect/ST — Writer/State/Either/Maybe chains — compiles to nested closure chains: measured 63x against the equivalent statement sequence (1e6 iterations of a 3-bind chain, PUC 5.1). magicDo covers Effect/ST only, and flattenDeepBinds fixes nesting depth (the parser limit), not cost. The cure is specialization: inline the concrete dictionary's bind/apply/map into the call site, beta-reduce, and let case-of-known-constructor (#177) collapse the constructor matches into straight-line code.
The current heuristic refuses by construction: isInlinableExpr accepts refs, literals, annotations, and used-once bindings — a dictionary method is a multi-use non-trivial lambda, so it is never inlined into call sites.
Approach
GHC-style budgeted call-site inlining: inline a multi-use binding into a specific saturated application site when (a) the head resolves to a lambda, including through ObjectProp of a known top-level dictionary literal, (b) the application is saturated with respect to manifest arity, and (c) the inlinee's size is within a tunable budget (the analogue of GHC's unfolding-use-threshold), with growth accounted per fixpoint round — the change-flag infrastructure from #144 is in place. Separately, projection through a Ref: ObjectProp (Ref dict) "bind" resolves to the method expression by looking at the binding's RHS without inlining the whole dictionary.
Prerequisites / Relations
The prerequisites that complete the cascade are #177 (known-constructor folds) and #178 (primops give arithmetic inside chains a bottom); with #177 alone the Maybe/Either case is already useful, collapsing into if-chains. Several open issues interact: #167 is a discipline prerequisite (without let-binding non-trivial arguments, aggressive inlining multiplies work); #143 must be fixed with or before this, since the heuristic stands on reference counts that go stale during inlining; #142 becomes visible because more inlining means more fixpoint pressure on optimizer compile time; and the budget must interact predictably with the @inline annotations of #169 and #171.
Verification / Measurement
The win is measured by #172's bind-chain macrobenchmark — the 63x case — with the LongWriterBind/LongStateBind goldens as ready-made material. Code growth is the main risk, bounded by the size budget and watched via golden.lua sizes. Because the transform is behaviour-preserving, runtime output must stay identical: the eval goldens must not move.
Problem
Monadic code outside Effect/ST — Writer/State/Either/Maybe chains — compiles to nested closure chains: measured 63x against the equivalent statement sequence (1e6 iterations of a 3-bind chain, PUC 5.1). magicDo covers Effect/ST only, and flattenDeepBinds fixes nesting depth (the parser limit), not cost. The cure is specialization: inline the concrete dictionary's
bind/apply/mapinto the call site, beta-reduce, and let case-of-known-constructor (#177) collapse the constructor matches into straight-line code.The current heuristic refuses by construction:
isInlinableExpraccepts refs, literals, annotations, and used-once bindings — a dictionary method is a multi-use non-trivial lambda, so it is never inlined into call sites.Approach
GHC-style budgeted call-site inlining: inline a multi-use binding into a specific saturated application site when (a) the head resolves to a lambda, including through
ObjectPropof a known top-level dictionary literal, (b) the application is saturated with respect to manifest arity, and (c) the inlinee's size is within a tunable budget (the analogue of GHC's unfolding-use-threshold), with growth accounted per fixpoint round — the change-flag infrastructure from #144 is in place. Separately, projection through a Ref:ObjectProp (Ref dict) "bind"resolves to the method expression by looking at the binding's RHS without inlining the whole dictionary.Prerequisites / Relations
The prerequisites that complete the cascade are #177 (known-constructor folds) and #178 (primops give arithmetic inside chains a bottom); with #177 alone the Maybe/Either case is already useful, collapsing into if-chains. Several open issues interact: #167 is a discipline prerequisite (without let-binding non-trivial arguments, aggressive inlining multiplies work); #143 must be fixed with or before this, since the heuristic stands on reference counts that go stale during inlining; #142 becomes visible because more inlining means more fixpoint pressure on optimizer compile time; and the budget must interact predictably with the
@inlineannotations of #169 and #171.Verification / Measurement
The win is measured by #172's bind-chain macrobenchmark — the 63x case — with the
LongWriterBind/LongStateBindgoldens as ready-made material. Code growth is the main risk, bounded by the size budget and watched viagolden.luasizes. Because the transform is behaviour-preserving, runtime output must stay identical: theevalgoldens must not move.