Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions bench/goldens/fnew_Bench.EffectStep.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
chunk: Bench.EffectStep.lua
runtime: LuaJIT 2.1.1741730670
main-chunk FNEW: 11
function-body FNEW: 13
total FNEW: 24
prototypes: 25
function-body FNEW sites:
Bench.EffectStep.lua:4
Bench.EffectStep.lua:6
Bench.EffectStep.lua:6
Bench.EffectStep.lua:9
Bench.EffectStep.lua:10
Bench.EffectStep.lua:12
Bench.EffectStep.lua:12
Bench.EffectStep.lua:33
Bench.EffectStep.lua:54
Bench.EffectStep.lua:53
Bench.EffectStep.lua:48
Bench.EffectStep.lua:57
Bench.EffectStep.lua:66
28 changes: 28 additions & 0 deletions bench/goldens/trace_effect_step.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
spec: effect_step
runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=1500000
aborts (distinct site -- reason):
Bench.EffectStep.lua:10 -- NYI: bytecode FNEW
Bench.EffectStep.lua:12 -- NYI: bytecode FNEW
Bench.EffectStep.lua:48 -- NYI: bytecode FNEW
Bench.EffectStep.lua:49 -- NYI: bytecode UCLO
Bench.EffectStep.lua:53 -- NYI: bytecode FNEW
Bench.EffectStep.lua:54 -- NYI: bytecode FNEW
Bench.EffectStep.lua:66 -- NYI: bytecode FNEW
Bench.EffectStep.lua:9 -- NYI: bytecode FNEW
bytecode end state (J*=compiled, I*=blacklisted):
Bench.EffectStep.lua:10 IFUNCF
Bench.EffectStep.lua:10 JFUNCF
Bench.EffectStep.lua:11 IFUNCF
Bench.EffectStep.lua:12 IFUNCF
Bench.EffectStep.lua:12 JFUNCF
Bench.EffectStep.lua:17 IFUNCF
Bench.EffectStep.lua:36 IFUNCF
Bench.EffectStep.lua:37 IFUNCF
Bench.EffectStep.lua:39 IFUNCF
Bench.EffectStep.lua:40 JFUNCF
Bench.EffectStep.lua:9 JFUNCF
effect_step.lua:11 JFUNCF
effect_step.lua:13 JFORI
effect_step.lua:13 JFORL
counts: aborts=8 compiled=7 blacklisted=7
16 changes: 16 additions & 0 deletions bench/macro/effect_step.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- A hot ST loop whose per-iteration step is a unary effect action, always
-- fully applied and immediately run. Uncurried late (after magicDo), the
-- step is one n-ary worker call per iteration; curried, every iteration
-- allocates the thunk closure and pays a second call to force it.
return {
artifact = "Bench.EffectStep",
n = 100000,
drive = function(mod, n)
return mod.run(n)
end,
ideal = function(n)
local acc = 0
for _ = 1, n do acc = acc + 15 end
return acc
end,
}
13 changes: 13 additions & 0 deletions changelog.d/20260713_160000_unisay_late_uncurry_rerun.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Changed

- The uncurrying worker/wrapper split (#24) now runs a second time at the end
of the IR pipeline, catching the two saturated-call families the early run
cannot see: effect functions whose thunk-forcing application only appears
once magic-do has run — the worker absorbs the thunk parameter, so a fully
applied effect statement compiles to one n-ary call instead of a curried
call plus a thunk allocation and force — and the `$kont` continuation
helpers minted by `flattenDeepBinds`, saturated by construction. The pass is
rerun-safe (wrappers built by the early run are recognised and reused, never
split again), effect statements keep their dead-code protection through the
rewrite (`isEffectRun` recognises the absorbed trailing marker), and a
closing dce pass drops the wrappers whose sites were all saturated (#200).
21 changes: 19 additions & 2 deletions lib/Language/PureScript/Backend/IR/Optimizer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,9 @@ optimizerPipeline policy =
, RunFixpoint "optimize+dce-post-merge" (optimizePass :| [dcePass])
, -- Split curried bindings into n-ary workers and curried wrappers
-- and rewrite the saturated call sites to direct worker calls
-- (issue #24). Runs after the post-merge fixpoint, so manifest
-- arities are measured once inlining has settled. See
-- (issue #24) — the early of the pass's two runs (the late one
-- closes the pipeline). Runs after the post-merge fixpoint, so
-- manifest arities are measured once inlining has settled. See
-- Language.PureScript.Backend.IR.Uncurry.
RunPass uncurryPass
, -- The post-uncurry fixpoint dead-code-eliminates wrappers with no
Expand Down Expand Up @@ -174,6 +175,21 @@ optimizerPipeline policy =
-- likewise consumes and preserves the unique naming.
-- See Language.PureScript.Backend.IR.FlattenDeepBinds.
RunPass flattenDeepBindsPass
, -- The late uncurry run (issue #200): the same pass again, now that
-- the two saturated-site families invisible to the early run exist —
-- the effect-run spines magicDo completed (f(a)(b)(run), saturating
-- the effect function's manifest chain, thunk parameter included)
-- and the saturated-by-construction $kont helpers minted by the
-- flattening above. Bindings the early run split are recognised and
-- left split (see the Rerun section in
-- Language.PureScript.Backend.IR.Uncurry).
RunPass uncurryLatePass
, -- Drop the wrappers the late run left unreferenced. A single dce
-- pass, deliberately not an optimize+dce fixpoint like the early
-- run's: optimize's use-once inlining would paste the $kont workers
-- (each is called exactly once) back into their call sites round by
-- round, re-nesting exactly what flattenDeepBinds just flattened.
RunPass dcePass
]
where
uniquifyPass =
Expand Down Expand Up @@ -222,6 +238,7 @@ optimizerPipeline policy =
, passRequires = guc
, passEnsures = guc
}
uncurryLatePass = uncurryPass {passName = "uncurry-late"}
floatInPass = gucPass "float-in" floatIn
shareAccessorsPass =
gucPass "share-accessors" (shareForeignAccessors policy)
Expand Down
12 changes: 8 additions & 4 deletions lib/Language/PureScript/Backend/IR/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,13 @@ pattern EffectRunArg ∷ ann → RawExp ann
pattern EffectRunArg ann =
Ref ann (Imported (ModuleName "Prim") (Name "$magicDoRun"))

{- | Recognise an Effect/ST statement as magic-do emits it: running a thunk, the
application @m EffectRunArg@ (see 'Language.PureScript.Backend.IR.MagicDo'). Its
side effect is observable and its statement sequencing is size-managed by
{- | Recognise an Effect/ST statement by its trailing 'EffectRunArg': running a
thunk, the application @m EffectRunArg@ as magic-do emits it (see
'Language.PureScript.Backend.IR.MagicDo'), or the n-ary worker call that
absorbed the run — the late uncurry rerun rewrites the saturated spine
@f(a)(b)(EffectRunArg)@ to @f$w(a, b, EffectRunArg)@ (issue #200). The marker
never occurs in a non-trailing position, so a trailing one is precise. The
statement's side effect is observable and its sequencing is size-managed by
magic-do's chunking, so passes that run after magic-do must leave it alone:
dead-code elimination keeps it even when its binder is unreferenced, beta
reduction does not reduce through it (which would merge a chunk into its parent
Expand All @@ -457,7 +461,7 @@ copy would execute the effect twice).
-}
isEffectRun ∷ RawExp ann → Bool
isEffectRun = \case
AppN _ _ (EffectRunArg _ :| []) → True
AppN _ _ args | EffectRunArg _ ← last args → True
_ → False

ctorId ∷ ModuleName → TyName → CtorName → Text
Expand Down
Loading