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
6 changes: 3 additions & 3 deletions bench/goldens/fnew_Bench.EffectStep.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ function-body FNEW sites:
Bench.EffectStep.lua:11
Bench.EffectStep.lua:11
Bench.EffectStep.lua:32
Bench.EffectStep.lua:43
Bench.EffectStep.lua:46
Bench.EffectStep.lua:55
Bench.EffectStep.lua:49
Bench.EffectStep.lua:48
Bench.EffectStep.lua:58
19 changes: 19 additions & 0 deletions bench/goldens/fnew_Bench.EffectStep2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
chunk: Bench.EffectStep2.lua
runtime: LuaJIT 2.1.1741730670
main-chunk FNEW: 11
function-body FNEW: 12
total FNEW: 23
prototypes: 24
function-body FNEW sites:
Bench.EffectStep2.lua:3
Bench.EffectStep2.lua:5
Bench.EffectStep2.lua:5
Bench.EffectStep2.lua:8
Bench.EffectStep2.lua:9
Bench.EffectStep2.lua:11
Bench.EffectStep2.lua:11
Bench.EffectStep2.lua:44
Bench.EffectStep2.lua:43
Bench.EffectStep2.lua:52
Bench.EffectStep2.lua:51
Bench.EffectStep2.lua:59
8 changes: 8 additions & 0 deletions bench/goldens/tnew_Bench.EffectStep2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
chunk: Bench.EffectStep2.lua
runtime: LuaJIT 2.1.1741730670
main-chunk TNEW+TDUP: 3
function-body TNEW+TDUP: 1
total TNEW+TDUP: 4
prototypes: 24
function-body TNEW+TDUP sites:
Bench.EffectStep2.lua:8 TDUP
8 changes: 3 additions & 5 deletions bench/goldens/trace_effect_step.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=1500000
aborts (distinct site -- reason):
Bench.EffectStep.lua:11 -- NYI: bytecode FNEW
Bench.EffectStep.lua:43 -- NYI: bytecode FNEW
Bench.EffectStep.lua:55 -- NYI: bytecode FNEW
Bench.EffectStep.lua:58 -- NYI: bytecode FNEW
Bench.EffectStep.lua:8 -- 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:11 IFUNCF
Bench.EffectStep.lua:11 JFUNCF
Bench.EffectStep.lua:16 IFUNCF
Bench.EffectStep.lua:35 IFUNCF
Bench.EffectStep.lua:36 IFUNCF
Bench.EffectStep.lua:35 ILOOP
Bench.EffectStep.lua:8 JFUNCF
Bench.EffectStep.lua:9 IFUNCF
Bench.EffectStep.lua:9 JFUNCF
counts: aborts=5 compiled=3 blacklisted=6
counts: aborts=4 compiled=3 blacklisted=5
18 changes: 18 additions & 0 deletions bench/goldens/trace_effect_step2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
spec: effect_step2
runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=1500000
aborts (distinct site -- reason):
Bench.EffectStep2.lua:11 -- NYI: bytecode FNEW
Bench.EffectStep2.lua:59 -- NYI: bytecode FNEW
Bench.EffectStep2.lua:8 -- NYI: bytecode FNEW
Bench.EffectStep2.lua:9 -- NYI: bytecode FNEW
bytecode end state (J*=compiled, I*=blacklisted):
Bench.EffectStep2.lua:10 IFUNCF
Bench.EffectStep2.lua:11 IFUNCF
Bench.EffectStep2.lua:11 JFUNCF
Bench.EffectStep2.lua:16 IFUNCF
Bench.EffectStep2.lua:30 ILOOP
Bench.EffectStep2.lua:8 JFUNCF
Bench.EffectStep2.lua:9 IFUNCF
Bench.EffectStep2.lua:9 JFUNCF
counts: aborts=4 compiled=3 blacklisted=5
19 changes: 19 additions & 0 deletions bench/macro/effect_step2.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- A hot ST loop whose per-iteration step is a two-argument effect action,
-- always fully applied and immediately run. Two real arguments saturate the
-- spine before magicDo runs, so the uncurry split fires at the real arity and
-- the thunk ends up inside the worker: every iteration allocates that closure
-- and pays a second call to force it. With the thunk parameter absorbed into
-- the worker the iteration is one n-ary call and no allocation.
-- `effect_step.lua` is the unary sibling, which the late uncurry run covers.
return {
artifact = "Bench.EffectStep2",
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,
}
83 changes: 83 additions & 0 deletions changelog.d/20260729_200000_unisay_absorb_effect_thunk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
### Added

- Effect/ST actions of two or more arguments no longer allocate a closure
per executed statement (#265). Such an action is already saturated at
its real arity when the uncurrying worker/wrapper split measures it, so
the split fires there and magic-do only afterwards rewrites the worker's
body into the nullary thunk an Effect value is — leaving every fully
applied statement site to allocate that thunk and immediately force it.
The late uncurry run cannot repair this: it splits manifest lambda
chains, and the thunk sits inside a worker that is already n-ary. The
new `absorbEffectThunk` pass instead widens the worker in place, moving
the thunk's parameter onto its parameter list, so the site is one n-ary
call:

```lua
local Golden_EffectWorkerThunk_Test_report_S_w = function(tag, n)
return function()
local _ = Effect_Console_log(tag)()
local _ = Effect_Console_log(Data_Show_showIntImpl(n))()
return Effect_Console_log("-")()
end
end
local _ = Golden_EffectWorkerThunk_Test_report_S_w("a", 1)()
```

becomes

```lua
local Golden_EffectWorkerThunk_Test_report_S_w = function(tag, n)
local _ = Effect_Console_log(tag)()
local _ = Effect_Console_log(Data_Show_showIntImpl(n))()
return Effect_Console_log("-")()
end
local _ = Golden_EffectWorkerThunk_Test_report_S_w("a", 1)
```

The action's curried wrapper grows one parameter so a partial
application still evaluates to a closure. Taking the run marker into
the call also makes a recursive driver's self-call a genuine tail call,
which the native-loop lowering then turns into a Lua `while` — the
driver of `Bench.EffectStep` went from

```lua
local Bench_EffectStep_go_S_w
Bench_EffectStep_go_S_w = function(i, ref)
return function()
local _ = Bench_EffectStep_step_S_w(ref)
if i >= 1 and i ~= 1 then
return Bench_EffectStep_go_S_w(i - 1, ref)()
else
return Control_Monad_ST_Internal_read(ref)()
end
end
end
```

to

```lua
local Bench_EffectStep_go_S_w = function(i, ref)
while true do
local _ = Bench_EffectStep_step_S_w(ref)
if i >= 1 and i ~= 1 then
i, ref = i - 1, ref
else
return Control_Monad_ST_Internal_read(ref)()
end
end
end
```

1.22× faster under PUC Lua 5.1 and 1.28× under LuaJIT on the new
`Bench.EffectStep2` macrobenchmark, and 1.10×/1.14× on the pre-existing
`Bench.EffectStep`, whose two-argument driver is the same case; that
spec's trace report loses one `NYI: bytecode FNEW` abort and its driver
ends compiled as an `ILOOP` instead of interpreted as an `IFUNCF`.

A worker whose call is bound as an action value and run later keeps the
call-then-force shape, as does any other reference the widened arity
would leave under-applied: the Lua backend drops a trailing unused
parameter run, so an under-applied worker call would run the effect at
construction time. `Golden.EffectWorkerThunk` pins both sides, including
a `let`-bound local worker.
Loading