Inline bare-primop workers at saturated call sites (#281) - #282
Merged
Conversation
Unisay
force-pushed
the
issue-281/inline-primop-workers
branch
from
July 16, 2026 10:18
1a3b751 to
a7b7dd6
Compare
Unisay
added a commit
that referenced
this pull request
Jul 16, 2026
- lib/Language/PureScript/Backend/IR/Optimizer.hs:1651 — reword the isBarePrimOpBody Haddock to state the actual criterion (operands classified Trivial by complexityOf), which is wider than the previously documented "parameter references or scalar literals" (#282 (comment))
Unisay
enabled auto-merge
July 16, 2026 11:39
) An n-ary worker call minted by the uncurry split is not a unary spine, so unwindApp left it whole and inlineSaturatedCall never saw it: a floated dictionary application (add = Data.Semiring.add semiringInt) that resolved to a manifest lambda before the early uncurry run kept paying a Lua function call per arithmetic operation in hot loops. A worker whose body is a bare primop over trivial operands is now pasted at every saturated n-ary call site — under the original AppN node, so the exact-arity beta reduction consumes it in the same pass.
- lib/Language/PureScript/Backend/IR/Optimizer.hs:1651 — reword the isBarePrimOpBody Haddock to state the actual criterion (operands classified Trivial by complexityOf), which is wider than the previously documented "parameter references or scalar literals" (#282 (comment))
The dissolved workers drop one function per affected artifact (FNEW census and prototype counts shrink, trace reports compile one function fewer at shifted line numbers); every workload result is unchanged.
Unisay
force-pushed
the
issue-281/inline-primop-workers
branch
from
July 16, 2026 12:12
335a2c8 to
49d0fab
Compare
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 #281.
Diagnosis
Traced the pipeline (
runStepsTraced) onBench.TupleFoldandGolden.SpecConstr.Testas the issue asked. The divergence betweenaddandsubcomes down to the dictionary's whole-program use count, not the method's. When the dictionary is referenced exactly once in the übermodule, the pre-uncurry optimize+dce fixpoints resolve the floated binding all the way to a manifest lambda:The early uncurry run then splits the now-manifest chain and rewrites the saturated sites to direct n-ary worker calls, and such a call is invisible to
inlineSaturatedCall:unwindApppeels only unaryApplinks, so the n-ary node stays whole in head position and matches no rule:When the dictionary is multiply-used instead, the whole-binding paste never claims it; the floated
addbinding itself dissolves (its RHS is a Deref-tier projection), each site keeps a curried spine over the projection, and the specialize fixpoint folds it:That is why
adddissolved inGolden.SpecConstr.Test(itssemiringIntis shared) whilesubsurvived (itsringIntis single-use), and the mirror image inBench.TupleFold— the issue's use-count hypothesis failed in both directions because the trigger sits one level up, on the dictionary.Fix
inlineSaturatedCallgets an n-ary case (the narrow fix the issue anticipated): a saturated n-ary call of a worker whose body is a bare primop over trivial operands (PrimBinOp/Eq/PrimNotthereof, operandsTrivial— parameter references or scalar literals) pastes the worker's lambda under the originalAppNnode, where the exact-aritybetaReduceconsumes it in the same bottom-up pass:Pasting under the
AppN— rather than throughrebuildSpinelike the unary paths — is what keepsWellAppliedintact, the concernpasteableRootdocuments: rebuilt as a unary spine the paste would be an under-applied redex the lint rejects and exact-arity beta reduction never repairs:Since every operand of a bare primop body occurs exactly once, beta reduction always substitutes and never leaves a residual
Let, so no IIFE can appear at the site.Verification
add = λx. λy. x + y— two, so the use-once inline cannot mask the bug — now leave no binding behind and fold both exports to the inlinePrimBinOp.Golden.SpecConstr.Test'ssub$wsites fold to inline-exactly as the issue asks; 46 structural goldens move — workers dissolve, and constant folding cascades where sites turned literal, e.g.add$w(add$w(3, 4), 5)→12inGolden.UncurryCtor.Test. All eval goldens are byte-identical.tuple_foldloop body (bench/_build/Bench.TupleFold.lua) is the issue's target shape:bench/runontuple_foldunder PUC Lua 5.1: 0.169s → 0.062s against the 0.056s ideal loop, closing the ~3x gap; the other macro benchmarks are unchanged or slightly improved.cabal test allpasses, plus randomized specs stressed across five fixed seeds.