Derive transitive inline directives on specialization bindings - #324
Merged
Conversation
Pipeline-level specs for the directive derivation on specialization bindings: a binding shaped f a1..ak where f carries @inline arity=N inherits always-inline when k >= N and arity=(N-k) when under-applied, transitively through chains, never overriding an explicit directive. The Golden.DirectiveDerived.Test module and its hand-computed eval oracle are committed in the pre-derivation state: golden.ir/golden.lua show the specialization surviving as a shared binding with un-inlined call sites, so the implementing commit carries the reviewable diff of what derivation changes. The unit specs are red at this commit.
A top-level binding whose settled shape applies an @inline arity=N target to k arguments inherits arity=(N-k) while under-applied and always-inline once saturated: a site applying N-k more arguments to the binding reconstructs, after the paste, a qualifying site of the explicit directive, so the derivation is the compositional reading of the arity policy (Note [Derived inline directives]). The pipeline is split into two phases around the derivation point (OptimizerPhases): derivedInlinePolicy reads the module settled by the post-merge optimize+dce fixpoint (use-once specializations are already dissolved), and the lower phase runs under the extended policy, so derived arities join the uncurry veto and the arity call-site gate, and derived always entries are pasted by the post-uncurry fixpoint. One left-to-right fold over the bindings derives transitively and bounds the derivation; explicit root directives are never overridden. Golden.DirectiveDerived.Test goldens move from the shared-binding state committed with the specs to the derived state: the saturated specialization is pasted and folds to constants at both sites, the under-applied one pastes at its applied site and stays a pinned shared binding for the bare use. The hand-written eval oracle is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #241.
Problem
With graduated
@inlinedirectives in place (#232), a directive names the generic combinator, but the bindings that actually appear at call sites are often its specializations: purs's common-subexpression pass floats a repeated dictionary application into a top-level binding (bind = Control.Bind.bind bindStateT, verbatim from real CoreFn output), and users write the same shape by hand as top-level partial applications. None of these carry a directive of their own, so each had to be annotated by hand for the directive to keep working through the indirection.Mechanism
The
arity=Npolicy composes over application, which is what makes derivation mechanical. For a bindinga = f x₁ … xₖwherefcarriesarity=N: whenk < N, a call site applyingN − kmore arguments toareconstructs — oncea's right-hand side is pasted there — a site applying at leastNarguments tof, exactly a qualifying site of the explicit directive, soainheritsarity=(N − k); whenk ≥ N, the right-hand side is itself already a qualifying call, so every use ofastands for the specialized value andainherits always-inline (thearity=0reading of the same arithmetic). An explicit directive on the specialization always wins over derivation. The rationale lives inNote [Derived inline directives].The optimizer pipeline is now split into two phases around the derivation point (
OptimizerPhases). Derivation reads the module settled by the post-merge optimize+dce fixpoint rather than the pristine input: a specialization referenced once has already dissolved into its use site by then, so only the shared survivors are inspected. It runs before the uncurry worker/wrapper split, so a derived arity joins the uncurry veto like an explicit one, and before the post-uncurry fixpoint, whose whole-binding inlining performs the derived always-inline pastes; derived arities paste at qualifying call sites in the specialize fixpoint exactly like explicit ones. One left-to-right fold over the bindings both derives transitively (a standalone binding references only earlier bindings, so a chain of specializations resolves in a single pass) and bounds the derivation.End to end
Golden.DirectiveDerived.Testcarries-- @inline combine arity=2on a three-parameter function too big for any heuristic paste, plus two pragma-free specializations:onePlusTwo = combine 1 2(saturated at the directed arity) andoneOnly = combine 1(one argument short). Before this change both survive as shared bindings and every site calls them (from the golden committed with the spec commit):After:
onePlusTwoderives always-inline, pastes at both sites and folds to constants;oneOnlyderivesarity=1, pastes at its applied site, and stays a pinned shared binding for the bare use that passes it to a function:The hand-written eval oracle (
-140,-40,-660,-246) passes in both states, so the transform is semantics-preserving. The corefn for this module also shows purs's CSE emitting genuine synthetic floats (sub = Data.Ring.sub ringInt,show = Data.Show.show showInt,discard = Control.Bind.discard discardUnit bindEffect) — the shapes this derivation exists for, once the default directive pack (#242) names their targets.Tests
Four pipeline-level specs (
derives directives for specializations) cover the saturated and under-applied derivations, transitivity through a chain (an inner specialization deriving from an outer one's derived entry, kept alive through the settle phase by a second use), and the explicit-directive precedence guard; three were red before the implementing commit. The golden module exercises the module-header pragma path end to end. No other golden moved: the corpus carries noarity=directives yet, so derivation fires nowhere else.