diff --git a/bench/goldens/fnew_Bench.EffectStep.txt b/bench/goldens/fnew_Bench.EffectStep.txt index 725cf89b..0b5eb6bf 100644 --- a/bench/goldens/fnew_Bench.EffectStep.txt +++ b/bench/goldens/fnew_Bench.EffectStep.txt @@ -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 diff --git a/bench/goldens/fnew_Bench.EffectStep2.txt b/bench/goldens/fnew_Bench.EffectStep2.txt new file mode 100644 index 00000000..71684b9f --- /dev/null +++ b/bench/goldens/fnew_Bench.EffectStep2.txt @@ -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 diff --git a/bench/goldens/tnew_Bench.EffectStep2.txt b/bench/goldens/tnew_Bench.EffectStep2.txt new file mode 100644 index 00000000..e08abb90 --- /dev/null +++ b/bench/goldens/tnew_Bench.EffectStep2.txt @@ -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 diff --git a/bench/goldens/trace_effect_step.txt b/bench/goldens/trace_effect_step.txt index f53dd352..e5fa05ae 100644 --- a/bench/goldens/trace_effect_step.txt +++ b/bench/goldens/trace_effect_step.txt @@ -3,8 +3,7 @@ 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): @@ -12,9 +11,8 @@ bytecode end state (J*=compiled, I*=blacklisted): 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 diff --git a/bench/goldens/trace_effect_step2.txt b/bench/goldens/trace_effect_step2.txt new file mode 100644 index 00000000..be91fc21 --- /dev/null +++ b/bench/goldens/trace_effect_step2.txt @@ -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 diff --git a/bench/macro/effect_step2.lua b/bench/macro/effect_step2.lua new file mode 100644 index 00000000..7c8d466b --- /dev/null +++ b/bench/macro/effect_step2.lua @@ -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, +} diff --git a/changelog.d/20260729_200000_unisay_absorb_effect_thunk.md b/changelog.d/20260729_200000_unisay_absorb_effect_thunk.md new file mode 100644 index 00000000..909ad530 --- /dev/null +++ b/changelog.d/20260729_200000_unisay_absorb_effect_thunk.md @@ -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. diff --git a/lib/Language/PureScript/Backend/IR/AbsorbEffectThunk.hs b/lib/Language/PureScript/Backend/IR/AbsorbEffectThunk.hs new file mode 100644 index 00000000..2e6d877b --- /dev/null +++ b/lib/Language/PureScript/Backend/IR/AbsorbEffectThunk.hs @@ -0,0 +1,304 @@ +{- | Absorb the magic-do thunk into an n-ary effect worker. + +The uncurrying worker\/wrapper split +("Language.PureScript.Backend.IR.Uncurry") runs before magic-do +("Language.PureScript.Backend.IR.MagicDo"), so an effect function of two +or more /real/ arguments is already saturated at that arity when the +split measures it: the worker takes the real arguments, and its body — +the @do@ block — is what magic-do later rewrites into a nullary thunk. +Every fully applied statement site therefore compiles to two Lua calls +with a closure allocation between them: + +> local whileE$w = function(cond, act) +> return function() … end -- the thunk magic-do built +> end +> local _ = whileE$w(cond, act)() -- allocate, then force + +The late uncurry run cannot repair this. It splits manifest chains of +unary lambdas, and the worker is already an 'AbsN' whose thunk sits +/inside/ the body, so there is no chain left to re-split; that run only +absorbs the thunk parameter of the effect actions the early run left +alone (the unary ones, below its arity floor). + +== The extension + +A qualifying worker is extended in place: the thunk's parameter joins +the worker's parameter list and the thunk's body becomes the worker's +body. + +> w = AbsN [p₁…pₙ] (λ_. body) ↦ w = AbsN [p₁…pₙ, _] body + +Each forced site loses its outer call — @w(a₁…aₙ)(run)@ becomes +@w(a₁…aₙ, run)@, which is still an effect run by its trailing marker +('Language.PureScript.Backend.IR.Types.isEffectRun') and still one Lua +call, because the backend erases that marker from an n-ary argument +list. So the site above becomes @local _ = whileE$w(cond, act)@: one +call, no closure. + +Each /wrapper/ — the curried delegate the split left under the original +name — grows by one parameter, which the delegate call passes on: + +> f = λf$p1.λf$p2. w(f$p1, f$p2) +> ↦ +> f = λf$p1.λf$p2.λf$p2$t. w(f$p1, f$p2, f$p2$t) + +A partial application @f a b@ therefore still evaluates to a closure — +the action — with the new innermost lambda playing the thunk's role, +and running it reaches the worker with the marker in place. The new +parameter is named after the wrapper's last one, which is unique within +its site, so the name is too, and it cannot collide with a source +identifier (no @$@ in PureScript identifiers). + +== The precondition + +The extension fires only for a worker that has at least one forced site +and whose every reference is either a forced site or a wrapper's +delegate call. Any other reference shape disqualifies it: the wider +arity would leave that site under-applied, and the backend drops the +worker's trailing unused parameter run, so an under-applied call is a +/saturated/ Lua call — it would run the effect at construction time +instead of returning the action. The shape that most often disqualifies +a binding is a worker call bound as an action value and run later, + +> local held = deferred$w("d", 4) -- the action, not yet run +> local _ = held() + +where extending @deferred$w@ would make the first line run the effect. + +== Scoping of the candidate maps + +Top-level candidates live in one module-wide map: after +'Language.PureScript.Backend.IR.Linker.qualifyTopRefs' every reference +to a top-level binding is 'Imported' and QNames are globally unique, so +a reference in any binding or export can reach any of them. 'Let'-bound +candidates are collected, qualified and rewritten independently per +top-level site, because local binder names are unique only per site +(see "Language.PureScript.Backend.IR.Uniquify") — a disqualifying +reference in one site must not veto a same-named worker of another. + +== Pipeline placement + +Last, after the late uncurry run and the dead-code pass that follows it. +Nothing after this point moves a call, so the reference census is final; +and the wrappers whose sites all went to their workers are already +gone, so no doomed wrapper is grown. The pass creates no dead code — it +removes no reference — so nothing needs to run after it. +-} +module Language.PureScript.Backend.IR.AbsorbEffectThunk + ( absorbEffectThunk + ) where + +import Control.Lens (cosmosOf, toListOf, transformOf) +import Data.List.NonEmpty qualified as NE +import Data.Map.Strict qualified as Map +import Language.PureScript.Backend.IR.Linker (UberModule (..)) +import Language.PureScript.Backend.IR.Names + ( Name (..) + , QName (..) + , Qualified (..) + , nameToText + ) +import Language.PureScript.Backend.IR.Types + ( Ann + , Exp + , Grouping + , Parameter (..) + , RawExp (..) + , getAnn + , listGrouping + , noAnn + , paramName + , refLocal + , setAnn + , subexpressions + , pattern Abs + , pattern App + , pattern EffectRunArg + ) +import Language.PureScript.Backend.IR.Uncurry (delegatesTo, manifestChain) + +{- | Extend every qualifying effect worker by the parameter of the thunk +its body returns, rewriting the forced call sites into single n-ary +calls and growing the wrappers that delegate to it. +-} +absorbEffectThunk ∷ UberModule → UberModule +absorbEffectThunk uber@UberModule {..} = + uber + { uberModuleBindings = extendTop <<$>> processedBindings + , uberModuleExports = second processSite <$> uberModuleExports + } + where + -- Every expression a reference to a top-level candidate can occur in. + siteExprs ∷ [Exp] + siteExprs = + (snd <$> (listGrouping =<< uberModuleBindings)) + <> (snd <$> uberModuleExports) + + topCandidates ∷ Map (Qualified Name) Int + topCandidates = + Map.fromList + [ (Imported modname name, length params) + | (QName modname name, expr) ← listGrouping =<< uberModuleBindings + , Just (params, _thunkParam, _body) ← [thunkWorker expr] + ] + + topExtended ∷ Map (Qualified Name) Int + topExtended = qualified topCandidates siteExprs + + -- The call sites first, the worker definitions second: extending a + -- worker changes its arity, which the site recognisers read off the + -- pre-extension candidate map. + processedBindings ∷ [Grouping (QName, Exp)] + processedBindings = fmap (second processSite) <$> uberModuleBindings + + extendTop ∷ (QName, Exp) → (QName, Exp) + extendTop (qname@(QName modname name), expr) + | Imported modname name `Map.member` topExtended = + (qname, extendWorker expr) + | otherwise = (qname, expr) + + -- Process one top-level site: qualify its Let-bound candidates by + -- their in-site references, then in one bottom-up sweep rewrite the + -- forced sites, grow the wrappers, and extend the local workers. + processSite ∷ Exp → Exp + processSite expr = transformOf subexpressions step expr + where + localCandidates ∷ Map (Qualified Name) Int + localCandidates = + Map.fromList + [ (Local name, length params) + | Let _ann groupings _body ← toListOf (cosmosOf subexpressions) expr + , (_memberAnn, name, rhs) ← listGrouping =<< toList groupings + , Just (params, _thunkParam, _body) ← [thunkWorker rhs] + ] + + extended ∷ Map (Qualified Name) Int + extended = topExtended <> qualified localCandidates [expr] + + -- Bottom-up is safe because no rewrite produces a node another + -- rewrite matches: a grown wrapper and an absorbed run both pass + -- one argument more than the recognisers accept, and an extended + -- local worker no longer returns a thunk. A wrapper is recognised + -- at its chain root, so the bottom-up sweep reaches the delegate + -- call inside it before the chain — and leaves it alone, since a + -- delegate call is not a forced site. + step ∷ Exp → Exp + step e = + fromMaybe e $ + (snd <$> forcedSite extended e) + <|> (snd <$> wrapper extended e) + <|> extendLocalWorkers extended e + + extendLocalWorkers ∷ Map (Qualified Name) Int → Exp → Maybe Exp + extendLocalWorkers arities = \case + Let ann groupings body + | any isExtended (listGrouping =<< toList groupings) → + Just (Let ann (fmap extend <$> groupings) body) + _ → Nothing + where + isExtended (_ann, name, _rhs) = Local name `Map.member` arities + extend member@(ann, name, rhs) + | Local name `Map.member` arities = (ann, name, extendWorker rhs) + | otherwise = member + +-------------------------------------------------------------------------------- +-- Recognition ----------------------------------------------------------------- + +{- | An n-ary worker whose body is a magic-do thunk: the worker's +parameters, the thunk's parameter and the thunk's body. + +The arity floor of two is the uncurrying split's own: a /unary/ chain +ending in a thunk is a manifest two-deep chain, which the late uncurry +run splits and absorbs itself. +-} +thunkWorker ∷ Exp → Maybe (NonEmpty (Parameter Ann), Parameter Ann, Exp) +thunkWorker = \case + AbsN _ann params (Abs _thunkAnn thunkParam@(ParamUnused _) body) + | length params >= 2 → Just (params, thunkParam, body) + _ → Nothing + +{- | Move the thunk's parameter onto the worker's parameter list, the +thunk's body becoming the worker's body. The parameter stays a trailing +'ParamUnused' run (Note [n-ary abstraction]) because it is appended. +-} +extendWorker ∷ Exp → Exp +extendWorker expr = case thunkWorker expr of + Just (params, thunkParam, body) → + AbsN (getAnn expr) (params <> one thunkParam) body + Nothing → expr + +{- | A forced site — a saturated call to a candidate that an effect run +immediately forces — paired with the single n-ary call replacing it. The +run marker joins the argument list, so the site stays an effect run +('Language.PureScript.Backend.IR.Types.isEffectRun') and the Lua backend +still erases it. +-} +forcedSite + ∷ Map (Qualified Name) Int → Exp → Maybe (Qualified Name, Exp) +forcedSite arities = \case + App ann (AppN _callAnn (Ref _refAnn q) args) run@(EffectRunArg _) + | Just arity ← Map.lookup q arities + , length args == arity → + Just (q, AppN ann (Ref noAnn q) (args <> one run)) + _ → Nothing + +{- | A wrapper of a candidate — a manifest chain of named unary lambdas +whose body is the delegate call on the chain's own parameters — paired +with the chain grown by one parameter, which the delegate passes on. + +Recognising and rewriting in one function keeps the reference census and +the rewrite from ever disagreeing about which references a wrapper +accounts for. +-} +wrapper ∷ Map (Qualified Name) Int → Exp → Maybe (Qualified Name, Exp) +wrapper arities expr = do + (params, body) ← manifestChain expr + (callAnn, refAnn, q, args) ← case body of + AppN callAnn (Ref refAnn q) args → Just (callAnn, refAnn, q, args) + _ → Nothing + arity ← Map.lookup q arities + guard (length params == arity) + guard (delegatesTo q params body) + thunkParam ← grownParam params + let delegate = + AppN callAnn (Ref refAnn q) (args <> one (refLocal thunkParam)) + grown = Abs noAnn (ParamNamed noAnn thunkParam) delegate + pure (q, setAnn (getAnn expr) (foldr (Abs noAnn) grown (toList params))) + +{- | The parameter a wrapper grows by, named after its last one — a name +unique within the site, so this one is too. 'Nothing' for an unnamed +last parameter, a shape 'delegatesTo' already rejects. +-} +grownParam ∷ NonEmpty (Parameter Ann) → Maybe Name +grownParam params = do + name ← paramName (NE.last params) + pure (Name (nameToText name <> "$t")) + +{- | The candidates whose references the extension can rewrite in full: +at least one forced site, and no reference outside the forced sites and +the wrappers. See the module haddock's Precondition section for why any +other reference shape must veto the whole binding. +-} +qualified + ∷ Map (Qualified Name) Int → [Exp] → Map (Qualified Name) Int +qualified candidates exprs = Map.filterWithKey accounted candidates + where + accounted ∷ Qualified Name → Int → Bool + accounted q _arity = + count forced q > 0 + && count refs q == count forced q + count wrappers q + + count ∷ Map (Qualified Name) Int → Qualified Name → Int + count = flip (Map.findWithDefault 0) + + nodes ∷ [Exp] + nodes = toListOf (cosmosOf subexpressions) =<< exprs + + refs, forced, wrappers ∷ Map (Qualified Name) Int + refs = tally [q | Ref _ann q ← nodes] + forced = tally (fst <$> mapMaybe (forcedSite candidates) nodes) + wrappers = tally (fst <$> mapMaybe (wrapper candidates) nodes) + + tally ∷ [Qualified Name] → Map (Qualified Name) Int + tally qs = + Map.fromListWith (+) [(q, 1) | q ← qs, q `Map.member` candidates] diff --git a/lib/Language/PureScript/Backend/IR/Optimizer.hs b/lib/Language/PureScript/Backend/IR/Optimizer.hs index 76c8924a..c2419005 100644 --- a/lib/Language/PureScript/Backend/IR/Optimizer.hs +++ b/lib/Language/PureScript/Backend/IR/Optimizer.hs @@ -10,6 +10,7 @@ import Data.Map qualified as Map import Data.Set qualified as Set import Data.Text qualified as Text import GHC.Generics (Generically (..)) +import Language.PureScript.Backend.IR.AbsorbEffectThunk (absorbEffectThunk) import Language.PureScript.Backend.IR.CSE (eliminateCommonSubexpressions) import Language.PureScript.Backend.IR.Cpr (cprWorkerWrapper) import Language.PureScript.Backend.IR.DCE (eliminateDeadCode) @@ -293,6 +294,16 @@ optimizerPipeline facts policy = OptimizerPhases {settlePhase, lowerPhase} -- (each is called exactly once) back into their call sites round by -- round, re-nesting exactly what flattenDeepBinds just flattened. RunPass dcePass + , -- Widen the effect workers the early uncurry run split at their + -- real arity, whose bodies magicDo then turned into thunks: the + -- thunk's parameter joins the worker, so a forced statement site + -- is one n-ary call instead of a worker call plus a run, with no + -- closure in between. Runs last — nothing after it moves a call, + -- so the reference census the precondition needs is final, and + -- the dce above has already dropped the wrappers whose sites all + -- went to their workers, so none of those is grown for nothing. + -- See Language.PureScript.Backend.IR.AbsorbEffectThunk. + RunPass absorbEffectThunkPass ] ctorTags ∷ CtorTagSets ctorTags = ctorTagSets dataTypes @@ -376,6 +387,8 @@ optimizerPipeline facts policy = OptimizerPhases {settlePhase, lowerPhase} , passEnsures = guc } magicDoPass = gucPass "magicDo" magicDo + absorbEffectThunkPass = + gucPass "absorb-effect-thunk" absorbEffectThunk flattenDeepBindsPass = Pass { passName = "flattenDeepBinds" diff --git a/lib/Language/PureScript/Backend/IR/Uncurry.hs b/lib/Language/PureScript/Backend/IR/Uncurry.hs index 23de0d7a..4835a7bd 100644 --- a/lib/Language/PureScript/Backend/IR/Uncurry.hs +++ b/lib/Language/PureScript/Backend/IR/Uncurry.hs @@ -104,6 +104,11 @@ idempotent. -} module Language.PureScript.Backend.IR.Uncurry ( uncurryWorkerWrapper + + -- * Shapes of the split + -- $shapes + , manifestChain + , delegatesTo ) where import Control.Lens (cosmosOf, toListOf, transformOf) @@ -137,6 +142,13 @@ import Language.PureScript.Backend.IR.Types , pattern Abs ) +{- $shapes +The two recognisers of the split's own output. +"Language.PureScript.Backend.IR.AbsorbEffectThunk" consumes the same +shapes — it widens a worker's arity, so it must agree with this pass on +what a worker's curried chain and its delegate call look like. +-} + {- | Split every qualifying binding into worker and wrapper and rewrite the saturated call sites to direct worker calls. The 'Set' argument is the @inline never@ veto collected by the optimizer; those bindings are diff --git a/pslua.cabal b/pslua.cabal index af0774dd..d1d64923 100644 --- a/pslua.cabal +++ b/pslua.cabal @@ -128,6 +128,7 @@ library exposed-modules: Language.PureScript.Backend Language.PureScript.Backend.IR + Language.PureScript.Backend.IR.AbsorbEffectThunk Language.PureScript.Backend.IR.Cpr Language.PureScript.Backend.IR.CSE Language.PureScript.Backend.IR.DCE @@ -190,6 +191,7 @@ test-suite spec main-is: Main.hs other-modules: Hedgehog.Gen.Extended + Language.PureScript.Backend.IR.AbsorbEffectThunk.Spec Language.PureScript.Backend.IR.Cpr.Spec Language.PureScript.Backend.IR.CSE.Spec Language.PureScript.Backend.IR.DCE.Spec diff --git a/test/Language/PureScript/Backend/IR/AbsorbEffectThunk/Spec.hs b/test/Language/PureScript/Backend/IR/AbsorbEffectThunk/Spec.hs new file mode 100644 index 00000000..8758a984 --- /dev/null +++ b/test/Language/PureScript/Backend/IR/AbsorbEffectThunk/Spec.hs @@ -0,0 +1,205 @@ +module Language.PureScript.Backend.IR.AbsorbEffectThunk.Spec where + +import Language.PureScript.Backend.IR.AbsorbEffectThunk (absorbEffectThunk) +import Language.PureScript.Backend.IR.Linker (UberModule (..)) +import Language.PureScript.Backend.IR.Names + ( ModuleName + , Name (..) + , QName (..) + , moduleNameFromString + ) +import Language.PureScript.Backend.IR.Types + ( Exp + , Grouping (..) + , abstraction + , abstractionN + , application + , applicationN + , eq + , lets + , literalInt + , noAnn + , paramNamed + , paramUnused + , refImported + , refLocal + , pattern EffectRunArg + ) +import Test.Hspec (Spec, describe, it, shouldBe) + +spec ∷ Spec +spec = describe "IR AbsorbEffectThunk" do + it "extends a worker whose only reference is a forced site" do + let m = + moduleOf + [Standalone (qn "f$w", thunkWorker2)] + [(Name "main", forcedSite)] + absorbEffectThunk m + `shouldBe` moduleOf + [Standalone (qn "f$w", extendedWorker2)] + [(Name "main", extendedCall)] + + it "grows the wrapper that delegates to the worker" do + let m = + moduleOf + [ Standalone (qn "f$w", thunkWorker2) + , Standalone (qn "f", wrapper2) + ] + [(Name "main", forcedSite)] + absorbEffectThunk m + `shouldBe` moduleOf + [ Standalone (qn "f$w", extendedWorker2) + , Standalone (qn "f", grownWrapper2) + ] + [(Name "main", extendedCall)] + + it "declines a worker with no forced site" do + -- The wrapper's delegate call alone buys nothing and would grow the + -- wrapper for free. + let m = + moduleOf + [ Standalone (qn "f$w", thunkWorker2) + , Standalone (qn "f", wrapper2) + ] + [(Name "main", application f (literalInt 1))] + absorbEffectThunk m `shouldBe` m + + it "declines a worker whose saturated call is not forced" do + -- The action is bound and run later: the wider arity would make the + -- binding itself run the effect. + let held = Name "held" + m = + moduleOf + [Standalone (qn "f$w", thunkWorker2)] + [ (Name "main", forcedSite) + , + ( Name "later" + , lets + (Standalone (noAnn, held, saturatedCall) :| []) + (application (refLocal held) (EffectRunArg noAnn)) + ) + ] + absorbEffectThunk m `shouldBe` m + + it "declines a worker passed on as a value" do + let m = + moduleOf + [Standalone (qn "f$w", thunkWorker2)] + [ (Name "main", forcedSite) + , (Name "value", fw) + ] + absorbEffectThunk m `shouldBe` m + + it "extends a Let-bound worker in place" do + let gw = Name "g$w" + localWorker = + abstractionN + (paramNamed (Name "a") :| [paramNamed (Name "b")]) + ( abstraction + paramUnused + (eq (refLocal (Name "a")) (refLocal (Name "b"))) + ) + localSite = + application + (applicationN (refLocal gw) (literalInt 1 :| [literalInt 2])) + (EffectRunArg noAnn) + siteOf worker = lets (Standalone (noAnn, gw, worker) :| []) + m = moduleOf [Standalone (qn "h", siteOf localWorker localSite)] [] + absorbEffectThunk m + `shouldBe` moduleOf + [ Standalone + ( qn "h" + , siteOf + ( abstractionN + ( paramNamed (Name "a") + :| [paramNamed (Name "b"), paramUnused] + ) + (eq (refLocal (Name "a")) (refLocal (Name "b"))) + ) + ( applicationN + (refLocal gw) + (literalInt 1 :| [literalInt 2, EffectRunArg noAnn]) + ) + ) + ] + [] + +-------------------------------------------------------------------------------- +-- Fixtures -------------------------------------------------------------------- + +mn ∷ ModuleName +mn = moduleNameFromString "Test" + +qn ∷ Text → QName +qn = QName mn . Name + +f ∷ Exp +f = refImported mn (Name "f") + +fw ∷ Exp +fw = refImported mn (Name "f$w") + +{- | @λa. λb. λ_. a == b@ — the worker as magic-do leaves it: n-ary in +its real arguments, its body the thunk. +-} +thunkWorker2 ∷ Exp +thunkWorker2 = + abstractionN + (paramNamed (Name "a") :| [paramNamed (Name "b")]) + (abstraction paramUnused (eq (refLocal (Name "a")) (refLocal (Name "b")))) + +-- | @λa. λb. λ_. a == b@ with the thunk's parameter absorbed. +extendedWorker2 ∷ Exp +extendedWorker2 = + abstractionN + (paramNamed (Name "a") :| [paramNamed (Name "b"), paramUnused]) + (eq (refLocal (Name "a")) (refLocal (Name "b"))) + +-- | @f$w(1, 2)@ — saturated in the real arguments, the action itself. +saturatedCall ∷ Exp +saturatedCall = applicationN fw (literalInt 1 :| [literalInt 2]) + +-- | @f$w(1, 2)(run)@ — the statement site magic-do produces. +forcedSite ∷ Exp +forcedSite = application saturatedCall (EffectRunArg noAnn) + +-- | @f$w(1, 2, run)@ — the same site as one call. +extendedCall ∷ Exp +extendedCall = + applicationN fw (literalInt 1 :| [literalInt 2, EffectRunArg noAnn]) + +-- | @λf$p1. λf$p2. f$w(f$p1, f$p2)@ — the curried delegate. +wrapper2 ∷ Exp +wrapper2 = + abstraction + (paramNamed (Name "f$p1")) + ( abstraction + (paramNamed (Name "f$p2")) + (applicationN fw (refLocal (Name "f$p1") :| [refLocal (Name "f$p2")])) + ) + +-- | The delegate grown by the parameter that now plays the thunk's role. +grownWrapper2 ∷ Exp +grownWrapper2 = + abstraction + (paramNamed (Name "f$p1")) + ( abstraction + (paramNamed (Name "f$p2")) + ( abstraction + (paramNamed (Name "f$p2$t")) + ( applicationN + fw + ( refLocal (Name "f$p1") + :| [refLocal (Name "f$p2"), refLocal (Name "f$p2$t")] + ) + ) + ) + ) + +moduleOf ∷ [Grouping (QName, Exp)] → [(Name, Exp)] → UberModule +moduleOf bindings exports = + UberModule + { uberModuleBindings = bindings + , uberModuleForeigns = [] + , uberModuleExports = exports + } diff --git a/test/Main.hs b/test/Main.hs index 02bd3cf6..53205526 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,5 +1,6 @@ module Main where +import Language.PureScript.Backend.IR.AbsorbEffectThunk.Spec qualified as AbsorbEffectThunk import Language.PureScript.Backend.IR.CSE.Spec qualified as CSE import Language.PureScript.Backend.IR.Cpr.Spec qualified as IRCpr import Language.PureScript.Backend.IR.DCE.Spec qualified as IrDce @@ -48,6 +49,7 @@ main = hspec do IRPass.spec IRCpr.spec IRUncurry.spec + AbsorbEffectThunk.spec IRUniquify.spec IRRenumber.spec LuaOptimizer.spec diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/corefn.json b/test/ps/output/Golden.EffectWorkerThunk.Test/corefn.json new file mode 100644 index 00000000..00e4a097 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/corefn.json @@ -0,0 +1 @@ +{"builtWith":"0.15.16","comments":[{"LineComment":" | Exercises absorbing the magic-do thunk into an early-split effect"},{"LineComment":" | worker (issue #265). An effect action of two or more real arguments"},{"LineComment":" | is saturated before magic-do runs, so the uncurry split fires at the"},{"LineComment":" | real arity and the worker's body is later rewritten into a thunk:"},{"LineComment":" | every statement site then reads `f$w(a, b)()`, one closure and one"},{"LineComment":" | extra call. `report` shows the plain win (its wrapper is dead, so"},{"LineComment":" | only the worker survives). `tally` is also passed as a value, so its"},{"LineComment":" | curried wrapper stays alive and must grow a parameter to keep a"},{"LineComment":" | partial application evaluating to a closure. `deferred` pins the"},{"LineComment":" | disqualified shape: its worker call is bound as an action value and"},{"LineComment":" | run later, a site that extending the arity would leave"},{"LineComment":" | under-applied, so the binding keeps the call-then-force shape."}],"decls":[{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[28,10],"start":[28,3]}},"type":"Var","value":{"identifier":"discard","moduleName":["Control","Bind"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discardUnit","moduleName":["Control","Bind"]}},"type":"App"},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"bindEffect","moduleName":["Effect"]}},"type":"App"},"identifier":"discard"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[29,10],"start":[29,3]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Effect","Console"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[29,18],"start":[29,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"showInt","moduleName":["Data","Show"]}},"type":"App"},"identifier":"logShow"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[29,15],"start":[29,14]}},"type":"Var","value":{"identifier":"mul","moduleName":["Data","Semiring"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[29,17],"start":[29,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"semiringInt","moduleName":["Data","Semiring"]}},"type":"App"},"identifier":"mul"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[35,15],"start":[35,14]}},"type":"Var","value":{"identifier":"add","moduleName":["Data","Semiring"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[35,19],"start":[35,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"semiringInt","moduleName":["Data","Semiring"]}},"type":"App"},"identifier":"add"},{"annotation":{"meta":null,"sourceSpan":{"end":[26,38],"start":[26,1]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[26,38],"start":[26,1]}},"argument":"tag","body":{"annotation":{"meta":null,"sourceSpan":{"end":[26,38],"start":[26,1]}},"argument":"n","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[28,6],"start":[28,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,10],"start":[28,7]}},"type":"Var","value":{"identifier":"tag","sourcePos":[27,1]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,10],"start":[28,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[29,18],"start":[29,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[29,18],"start":[29,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"mul","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[29,17],"start":[29,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[29,13],"start":[29,12]}},"type":"Var","value":{"identifier":"n","sourcePos":[27,1]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[29,17],"start":[29,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[29,17],"start":[29,16]}},"type":"Literal","value":{"literalType":"IntLiteral","value":2}},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[29,18],"start":[29,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[29,18],"start":[29,3]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[30,6],"start":[30,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[30,10],"start":[30,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[30,10],"start":[30,7]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"="}},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"Abs"},"identifier":"tally"},{"annotation":{"meta":null,"sourceSpan":{"end":[38,57],"start":[38,1]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[38,57],"start":[38,1]}},"argument":"f","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[40,12],"start":[40,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[40,4],"start":[40,3]}},"type":"Var","value":{"identifier":"f","sourcePos":[39,1]}},"annotation":{"meta":null,"sourceSpan":{"end":[40,10],"start":[40,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[40,10],"start":[40,5]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"via"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[40,12],"start":[40,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[40,12],"start":[40,11]}},"type":"Literal","value":{"literalType":"IntLiteral","value":3}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[40,12],"start":[40,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[40,12],"start":[40,3]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[41,6],"start":[41,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,12],"start":[41,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,12],"start":[41,7]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"ran"}},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"identifier":"runWith"},{"annotation":{"meta":null,"sourceSpan":{"end":[20,39],"start":[20,1]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[20,39],"start":[20,1]}},"argument":"tag","body":{"annotation":{"meta":null,"sourceSpan":{"end":[20,39],"start":[20,1]}},"argument":"n","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[22,10],"start":[22,3]}},"argument":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[22,6],"start":[22,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[22,10],"start":[22,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[22,10],"start":[22,7]}},"type":"Var","value":{"identifier":"tag","sourcePos":[21,1]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[22,10],"start":[22,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[22,10],"start":[22,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[23,12],"start":[23,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[23,12],"start":[23,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[23,12],"start":[23,11]}},"type":"Var","value":{"identifier":"n","sourcePos":[21,1]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[23,12],"start":[23,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[23,12],"start":[23,3]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[24,6],"start":[24,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[24,10],"start":[24,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[24,10],"start":[24,7]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"-"}},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"Abs"},"identifier":"report"},{"annotation":{"meta":null,"sourceSpan":{"end":[32,41],"start":[32,1]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[32,41],"start":[32,1]}},"argument":"tag","body":{"annotation":{"meta":null,"sourceSpan":{"end":[32,41],"start":[32,1]}},"argument":"n","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[34,10],"start":[34,3]}},"argument":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[34,6],"start":[34,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[34,10],"start":[34,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[34,10],"start":[34,7]}},"type":"Var","value":{"identifier":"tag","sourcePos":[33,1]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[34,10],"start":[34,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[34,10],"start":[34,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,20],"start":[35,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,20],"start":[35,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"add","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,19],"start":[35,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,13],"start":[35,12]}},"type":"Var","value":{"identifier":"n","sourcePos":[33,1]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[35,19],"start":[35,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,19],"start":[35,16]}},"type":"Literal","value":{"literalType":"IntLiteral","value":100}},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[35,20],"start":[35,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,20],"start":[35,3]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[36,6],"start":[36,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[36,10],"start":[36,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[36,10],"start":[36,7]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"+"}},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"Abs"},"identifier":"deferred"},{"annotation":{"meta":null,"sourceSpan":{"end":[43,20],"start":[43,1]}},"bindType":"NonRec","expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,15],"start":[45,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[45,9],"start":[45,3]}},"type":"Var","value":{"identifier":"report","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,13],"start":[45,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,13],"start":[45,10]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"a"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,15],"start":[45,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,15],"start":[45,14]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,15],"start":[45,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,15],"start":[45,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[46,15],"start":[46,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[46,9],"start":[46,3]}},"type":"Var","value":{"identifier":"report","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[46,13],"start":[46,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[46,13],"start":[46,10]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"b"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[46,15],"start":[46,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[46,15],"start":[46,14]}},"type":"Literal","value":{"literalType":"IntLiteral","value":2}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[46,15],"start":[46,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[46,15],"start":[46,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[47,14],"start":[47,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[47,8],"start":[47,3]}},"type":"Var","value":{"identifier":"tally","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[47,12],"start":[47,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[47,12],"start":[47,9]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"c"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[47,14],"start":[47,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[47,14],"start":[47,13]}},"type":"Literal","value":{"literalType":"IntLiteral","value":3}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[47,14],"start":[47,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[47,14],"start":[47,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[48,16],"start":[48,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[48,10],"start":[48,3]}},"type":"Var","value":{"identifier":"runWith","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[48,16],"start":[48,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[48,16],"start":[48,11]}},"type":"Var","value":{"identifier":"tally","moduleName":["Golden","EffectWorkerThunk","Test"]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[48,16],"start":[48,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[48,16],"start":[48,3]}},"argument":"$__unused","body":{"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[49,3]}},"binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[49,7]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[49,7]}},"argument":"a","body":{"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[49,7]}},"argument":"b","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,14],"start":[50,9]}},"argument":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[50,12],"start":[50,9]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,14],"start":[50,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,14],"start":[50,13]}},"type":"Var","value":{"identifier":"a","sourcePos":[49,7]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[50,14],"start":[50,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,14],"start":[50,9]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[51,25],"start":[51,9]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[51,25],"start":[51,9]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"mul","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[51,24],"start":[51,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[51,19],"start":[51,18]}},"type":"Var","value":{"identifier":"b","sourcePos":[49,7]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[51,24],"start":[51,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[51,24],"start":[51,22]}},"type":"Literal","value":{"literalType":"IntLiteral","value":10}},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[51,25],"start":[51,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[51,25],"start":[51,9]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[52,12],"start":[52,9]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[52,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[52,16],"start":[52,13]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"*"}},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"Abs"},"identifier":"step"}],"expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[53,13],"start":[53,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[53,7],"start":[53,3]}},"type":"Var","value":{"identifier":"step","sourcePos":[49,7]}},"annotation":{"meta":null,"sourceSpan":{"end":[53,11],"start":[53,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[53,11],"start":[53,8]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"e"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[53,13],"start":[53,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[53,13],"start":[53,12]}},"type":"Literal","value":{"literalType":"IntLiteral","value":5}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[53,13],"start":[53,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[53,13],"start":[53,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[54,13],"start":[54,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[54,7],"start":[54,3]}},"type":"Var","value":{"identifier":"step","sourcePos":[49,7]}},"annotation":{"meta":null,"sourceSpan":{"end":[54,11],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[54,11],"start":[54,8]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"f"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[54,13],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[54,13],"start":[54,12]}},"type":"Literal","value":{"literalType":"IntLiteral","value":6}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[54,13],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[54,13],"start":[54,3]}},"argument":"$__unused","body":{"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,3]}},"binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,7]}},"bindType":"NonRec","expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[55,22],"start":[55,14]}},"type":"Var","value":{"identifier":"deferred","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[55,26],"start":[55,14]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[55,26],"start":[55,23]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"d"}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,14]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,27]}},"type":"Literal","value":{"literalType":"IntLiteral","value":4}},"type":"App"},"identifier":"held"}],"expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,3]}},"argument":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[56,6],"start":[56,3]}},"type":"Var","value":{"identifier":"log","moduleName":["Effect","Console"]}},"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,7]}},"type":"Literal","value":{"literalType":"StringLiteral","value":"before held"}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","EffectWorkerThunk","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[57,7],"start":[57,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[57,7],"start":[57,3]}},"type":"Var","value":{"identifier":"held","sourcePos":[55,7]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[57,7],"start":[57,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[57,7],"start":[57,3]}},"argument":"$__unused","body":{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[58,3]}},"type":"Var","value":{"identifier":"held","sourcePos":[55,7]}},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Let"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Let"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"identifier":"main"}],"exports":["report","tally","deferred","runWith","main"],"foreign":[],"imports":[{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Control","Bind"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Data","Semiring"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Data","Show"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Effect"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Effect","Console"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Golden","EffectWorkerThunk","Test"]},{"annotation":{"meta":null,"sourceSpan":{"end":[15,15],"start":[15,1]}},"moduleName":["Prelude"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,7],"start":[13,1]}},"moduleName":["Prim"]}],"moduleName":["Golden","EffectWorkerThunk","Test"],"modulePath":"src/Golden/EffectWorkerThunk/Test.purs","reExports":{},"sourceSpan":{"end":[58,7],"start":[13,1]}} \ No newline at end of file diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/eval/.gitignore b/test/ps/output/Golden.EffectWorkerThunk.Test/eval/.gitignore new file mode 100644 index 00000000..d2dc29bb --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/eval/.gitignore @@ -0,0 +1 @@ +actual.txt diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/eval/golden.txt b/test/ps/output/Golden.EffectWorkerThunk.Test/eval/golden.txt new file mode 100644 index 00000000..f493b581 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/eval/golden.txt @@ -0,0 +1,26 @@ +a +1 +- +b +2 +- +c +6 += +via +6 += +ran +e +50 +* +f +60 +* +before held +d +104 ++ +d +104 ++ diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir new file mode 100644 index 00000000..ba576276 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir @@ -0,0 +1,443 @@ +UberModule + { uberModuleBindings = + [ Standalone + ( QName + { qnameModuleName = ModuleName "Data.Show", qnameName = Name "showIntImpl" + }, ObjectProp Nothing + ( ForeignImport Nothing + ( ModuleName "Data.Show" ) ".spago/p/prelude/5718c84fdde6247749cb053e816df696c30fe691/src/Data/Show.purs" + [ ( Nothing, Name "showIntImpl" ) ] + ) + ( PropName "showIntImpl" ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Effect.Console", qnameName = Name "log" + }, ObjectProp Nothing + ( ForeignImport Nothing + ( ModuleName "Effect.Console" ) ".spago/p/console/f82835a0b873aafe6bd7b14dd30cc150553d4ab9/src/Effect/Console.purs" + [ ( Nothing, Name "log" ) ] + ) + ( PropName "log" ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "tally$w" + }, AbsN Nothing + ( ParamNamed Nothing + ( Name "tag" ) :| + [ ParamNamed Nothing ( Name "n" ), ParamUnused Nothing ] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( Ref Nothing ( Local ( Name "tag" ) ) :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( PrimBinOp Nothing PrimMul + ( Ref Nothing ( Local ( Name "n" ) ) ) + ( LiteralInt Nothing 2 ) :| [] + ) :| [] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "=" :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "tally" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "tally$p1" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "tally$p2" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "tally$p2$t" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "tally$p1" ) ) :| + [ Ref Nothing + ( Local ( Name "tally$p2" ) ), Ref Nothing + ( Local ( Name "tally$p2$t" ) ) + ] + ) + ) + ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "runWith" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "f" ) :| [] ) + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Local ( Name "f" ) ) ) + ( LiteralString Nothing "via" :| [] ) + ) + ( LiteralInt Nothing 3 :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| [] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "ran" :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "report$w" + }, AbsN Nothing + ( ParamNamed Nothing + ( Name "tag" ) :| + [ ParamNamed Nothing ( Name "n" ), ParamUnused Nothing ] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( Ref Nothing ( Local ( Name "tag" ) ) :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( Ref Nothing ( Local ( Name "n" ) ) :| [] ) :| [] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "-" :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "deferred$w" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "tag" ) :| [ ParamNamed Nothing ( Name "n" ) ] ) + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( Ref Nothing ( Local ( Name "tag" ) ) :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) + ) + ( PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "n" ) ) ) + ( LiteralInt Nothing 100 ) :| [] + ) :| [] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "+" :| [] ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ) + ) + ], uberModuleForeigns = [], uberModuleExports = + [ + ( Name "report", AbsN Nothing + ( ParamNamed Nothing ( Name "report$p1$0" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "report$p2$0" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "report$p2$0$t" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "report$p1$0" ) ) :| + [ Ref Nothing + ( Local ( Name "report$p2$0" ) ), Ref Nothing + ( Local ( Name "report$p2$0$t" ) ) + ] + ) + ) + ) + ) + ), + ( Name "tally", Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally" ) ) + ), + ( Name "deferred", AbsN Nothing + ( ParamNamed Nothing ( Name "deferred$p1$0" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "deferred$p2$0" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "deferred$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "deferred$p1$0" ) ) :| + [ Ref Nothing ( Local ( Name "deferred$p2$0" ) ) ] + ) + ) + ) + ), + ( Name "runWith", Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "runWith" ) ) + ), + ( Name "main", AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) + ) + ( LiteralString Nothing "a" :| + [ LiteralInt Nothing 1, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) + ) + ( LiteralString Nothing "b" :| + [ LiteralInt Nothing 2, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ), Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) + ) + ( LiteralString Nothing "c" :| + [ LiteralInt Nothing 3, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ), Standalone + ( Nothing, Name "_", AppN Nothing + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Golden.EffectWorkerThunk.Test" ) + ( Name "tally$w" ) + ) + ) + ( LiteralString Nothing "via" :| + [ LiteralInt Nothing 3, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ) :| [] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "ran" :| [] ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ] + ) + ( AppN Nothing + ( Let Nothing + ( Standalone + ( Nothing, Name "step$0$w", AbsN Nothing + ( ParamNamed Nothing + ( Name "a$0" ) :| + [ ParamNamed Nothing ( Name "b$0" ), ParamUnused Nothing ] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) + ) + ( Ref Nothing ( Local ( Name "a$0" ) ) :| [] ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) + ) + ( PrimBinOp Nothing PrimMul + ( Ref Nothing ( Local ( Name "b$0" ) ) ) + ( LiteralInt Nothing 10 ) :| [] + ) :| [] + ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) + ( LiteralString Nothing "*" :| [] ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ) + ) :| [] + ) + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing ( Local ( Name "step$0$w" ) ) ) + ( LiteralString Nothing "e" :| + [ LiteralInt Nothing 5, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing ( Local ( Name "step$0$w" ) ) ) + ( LiteralString Nothing "f" :| + [ LiteralInt Nothing 6, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] + ) + ) + ] + ) + ( AppN Nothing + ( Let Nothing + ( Standalone + ( Nothing, Name "held$0", AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Golden.EffectWorkerThunk.Test" ) + ( Name "deferred$w" ) + ) + ) + ( LiteralString Nothing "d" :| [ LiteralInt Nothing 4 ] ) + ) :| [] + ) + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) + ) + ( LiteralString Nothing "before held" :| [] ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( Ref Nothing ( Local ( Name "held$0" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ] + ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "held$0" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ) + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ) + ] + } \ No newline at end of file diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua new file mode 100644 index 00000000..3ffa4cf3 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua @@ -0,0 +1,53 @@ +local M = {} +local Data_Show_showIntImpl = function(n) return tostring(n) end +local Effect_Console_log = function(s) return function() print(s) end end +local Golden_EffectWorkerThunk_Test_tally_S_w = function(tag, n) + local _ = Effect_Console_log(tag)() + local _ = Effect_Console_log(Data_Show_showIntImpl(n * 2))() + return Effect_Console_log("=")() +end +M.Golden_EffectWorkerThunk_Test_tally = function(tally_S_p1) + return function(tally_S_p2) + return function(tally_S_p2_S_t) + return Golden_EffectWorkerThunk_Test_tally_S_w(tally_S_p1, tally_S_p2, tally_S_p2_S_t) + end + end +end +M.Golden_EffectWorkerThunk_Test_runWith = function(f) + return function() + local _ = f("via")(3)() + return Effect_Console_log("ran")() + end +end +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_deferred_S_w = function(tag, n) + return function() + local _ = Effect_Console_log(tag)() + local _ = Effect_Console_log(Data_Show_showIntImpl(n + 100))() + return Effect_Console_log("+")() + end +end +return (function() + local _ = Golden_EffectWorkerThunk_Test_report_S_w("a", 1) + local _ = Golden_EffectWorkerThunk_Test_report_S_w("b", 2) + local _ = Golden_EffectWorkerThunk_Test_tally_S_w("c", 3) + local _ = (function() + local _ = Golden_EffectWorkerThunk_Test_tally_S_w("via", 3) + return Effect_Console_log("ran")() + end)() + local step_S_0_S_w = function(a_S_0, b_S_0) + local _ = Effect_Console_log(a_S_0)() + local _ = Effect_Console_log(Data_Show_showIntImpl(b_S_0 * 10))() + return Effect_Console_log("*")() + end + local _ = step_S_0_S_w("e", 5) + local _ = step_S_0_S_w("f", 6) + local held_S_0 = Golden_EffectWorkerThunk_Test_deferred_S_w("d", 4) + local _ = Effect_Console_log("before held")() + local _ = held_S_0() + return held_S_0() +end)() diff --git a/test/ps/output/Golden.NativeLoopsGuard.Test/golden.ir b/test/ps/output/Golden.NativeLoopsGuard.Test/golden.ir index 0c0873b1..62b5f5ae 100644 --- a/test/ps/output/Golden.NativeLoopsGuard.Test/golden.ir +++ b/test/ps/output/Golden.NativeLoopsGuard.Test/golden.ir @@ -269,34 +269,32 @@ UberModule ( QName { qnameModuleName = ModuleName "Golden.NativeLoopsGuard.Test", qnameName = Name "whileE$w" }, AbsN Nothing - ( ParamNamed Nothing ( Name "cond" ) :| [ ParamNamed Nothing ( Name "act" ) ] ) - ( AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Let Nothing - ( Standalone - ( Nothing, Name "b", AppN Nothing - ( Ref Nothing ( Local ( Name "cond" ) ) ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) - ) :| [] + ( ParamNamed Nothing + ( Name "cond" ) :| + [ ParamNamed Nothing ( Name "act" ), ParamUnused Nothing ] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "b", AppN Nothing + ( Ref Nothing ( Local ( Name "cond" ) ) ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| [] + ) + ( IfThenElse Nothing + ( Ref Nothing ( Local ( Name "b" ) ) ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "act" ) ) ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ) - ( IfThenElse Nothing - ( Ref Nothing ( Local ( Name "b" ) ) ) - ( AppN Nothing - ( Ref Nothing ( Local ( Name "act" ) ) ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) - ) + ( AppN Nothing ( AppN Nothing - ( AppN Nothing - ( ObjectProp Nothing - ( Ref Nothing - ( Imported ( ModuleName "Effect" ) ( Name "applicativeEffect" ) ) - ) - ( PropName "pure" ) - ) - ( Ref Nothing ( Imported ( ModuleName "Data.Unit" ) ( Name "unit" ) ) :| [] ) + ( ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect" ) ( Name "applicativeEffect" ) ) ) + ( PropName "pure" ) ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ( Ref Nothing ( Imported ( ModuleName "Data.Unit" ) ( Name "unit" ) ) :| [] ) ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ) ) ) @@ -492,128 +490,122 @@ UberModule ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ), Standalone ( Nothing, Name "_", AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.NativeLoopsGuard.Test" ) ( Name "whileE$w" ) ) + ) ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.NativeLoopsGuard.Test" ) ( Name "whileE$w" ) ) - ) ( AppN Nothing - ( AppN Nothing - ( ObjectProp Nothing - ( Ref Nothing - ( Imported ( ModuleName "Effect" ) ( Name "functorEffect" ) ) + ( ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect" ) ( Name "functorEffect" ) ) ) + ( PropName "map" ) + ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "v$0" ) :| [] ) + ( PrimBinOp Nothing PrimAnd + ( PrimBinOp Nothing PrimGe + ( Ref Nothing ( Local ( Name "v$0" ) ) ) + ( LiteralInt Nothing 0 ) ) - ( PropName "map" ) - ) - ( AbsN Nothing - ( ParamNamed Nothing ( Name "v$0" ) :| [] ) - ( PrimBinOp Nothing PrimAnd - ( PrimBinOp Nothing PrimGe + ( PrimNot Nothing + ( Eq Nothing ( Ref Nothing ( Local ( Name "v$0" ) ) ) ( LiteralInt Nothing 0 ) ) - ( PrimNot Nothing - ( Eq Nothing - ( Ref Nothing ( Local ( Name "v$0" ) ) ) - ( LiteralInt Nothing 0 ) + ) + ) :| [] + ) + ) + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Ref" ) ( Name "read" ) ) ) + ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) :| [] + ) :| + [ AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "n$0", AppN Nothing + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Effect.Ref" ) ( Name "read" ) ) ) + ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) ) - ) :| [] - ) - ) - ( AppN Nothing - ( Ref Nothing ( Imported ( ModuleName "Effect.Ref" ) ( Name "read" ) ) ) - ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) :| [] - ) :| - [ AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Let Nothing - ( Standalone - ( Nothing, Name "n$0", AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing ( AppN Nothing ( Ref Nothing - ( Imported ( ModuleName "Effect.Ref" ) ( Name "read" ) ) + ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) ) - ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) - ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] - ) - ) :| - [ Standalone - ( Nothing, Name "_", AppN Nothing ( AppN Nothing ( Ref Nothing - ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) - ) - ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) - ) - ( Ref Nothing ( Local ( Name "n$0" ) ) :| [] ) :| [] + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) + ( Ref Nothing ( Local ( Name "n$0" ) ) :| [] ) :| [] ) + ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( AppN Nothing + ( ObjectProp Nothing ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( Imported ( ModuleName "Effect" ) ( Name "functorEffect" ) ) ) + ( PropName "map" ) ) - ] - ) - ( AppN Nothing + ( AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Ref Nothing + ( Imported ( ModuleName "Data.Unit" ) ( Name "unit" ) ) + ) :| [] + ) + ) ( AppN Nothing ( AppN Nothing ( ObjectProp Nothing ( Ref Nothing - ( Imported ( ModuleName "Effect" ) ( Name "functorEffect" ) ) + ( Imported ( ModuleName "Effect.Ref" ) ( Name "foreign" ) ) ) - ( PropName "map" ) + ( PropName "modifyImpl" ) ) ( AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Ref Nothing - ( Imported ( ModuleName "Data.Unit" ) ( Name "unit" ) ) - ) :| [] - ) - ) - ( AppN Nothing - ( AppN Nothing - ( ObjectProp Nothing - ( Ref Nothing - ( Imported ( ModuleName "Effect.Ref" ) ( Name "foreign" ) ) + ( ParamNamed Nothing ( Name "s$0" ) :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "s'$0", PrimBinOp Nothing PrimSub + ( Ref Nothing ( Local ( Name "s$0" ) ) ) + ( LiteralInt Nothing 1 ) + ) :| [] ) - ( PropName "modifyImpl" ) - ) - ( AbsN Nothing - ( ParamNamed Nothing ( Name "s$0" ) :| [] ) - ( Let Nothing - ( Standalone - ( Nothing, Name "s'$0", PrimBinOp Nothing PrimSub - ( Ref Nothing ( Local ( Name "s$0" ) ) ) - ( LiteralInt Nothing 1 ) - ) :| [] - ) - ( LiteralObject Nothing - [ - ( PropName "state", Ref Nothing - ( Local ( Name "s'$0" ) ) - ), - ( PropName "value", Ref Nothing ( Local ( Name "s'$0" ) ) ) - ] - ) - ) :| [] - ) + ( LiteralObject Nothing + [ + ( PropName "state", Ref Nothing ( Local ( Name "s'$0" ) ) ), + ( PropName "value", Ref Nothing ( Local ( Name "s'$0" ) ) ) + ] + ) + ) :| [] ) - ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) :| [] ) - ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( Ref Nothing ( Local ( Name "r$0" ) ) :| [] ) :| [] ) ) + ( Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ) ) - ] - ) + ), Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ) ] ) diff --git a/test/ps/output/Golden.NativeLoopsGuard.Test/golden.lua b/test/ps/output/Golden.NativeLoopsGuard.Test/golden.lua index b733a5d9..bb131b1d 100644 --- a/test/ps/output/Golden.NativeLoopsGuard.Test/golden.lua +++ b/test/ps/output/Golden.NativeLoopsGuard.Test/golden.lua @@ -91,13 +91,11 @@ local Golden_NativeLoopsGuard_Test_logShow = function(a_S_2) return Effect_Console_log(Data_Show_showIntImpl(a_S_2)) end local Golden_NativeLoopsGuard_Test_whileE_S_w = function(cond, act) - return function() - local b = cond() - if b then - return act() - else - return Effect_applicativeEffect.pure(Data_Unit_unit)() - end + local b = cond() + if b then + return act() + else + return Effect_applicativeEffect.pure(Data_Unit_unit)() end end local Golden_NativeLoopsGuard_Test_foreachE_S_w = function(xs, f) @@ -135,6 +133,6 @@ return (function() local sPrime_S_0 = s_S_0 - 1 return { state = sPrime_S_0, value = sPrime_S_0 } end)(r_S_0))() - end)() + end) return Effect_Console_log("guard done")() end)() diff --git a/test/ps/src/Bench/EffectStep2.purs b/test/ps/src/Bench/EffectStep2.purs new file mode 100644 index 00000000..5f76bf97 --- /dev/null +++ b/test/ps/src/Bench/EffectStep2.purs @@ -0,0 +1,47 @@ +-- | A hot ST loop through a *two*-argument effect step that is always +-- | fully applied and immediately run — the case the late uncurry run +-- | (issue #200) cannot reach. With two real arguments the step's spine +-- | is already saturated when the early uncurry run measures it, so the +-- | split fires at the real arity and magicDo then rewrites the worker's +-- | body into a thunk: every iteration allocates that closure and pays +-- | the second call that forces it. Absorbing the thunk parameter into +-- | the worker makes the iteration one n-ary call with no allocation. +-- | `Bench.EffectStep` is the unary sibling, which the late run already +-- | handles. +module Bench.EffectStep2 where + +import Prelude + +import Control.Monad.ST (ST) +import Control.Monad.ST as ST +import Control.Monad.ST.Ref (STRef) +import Control.Monad.ST.Ref as STRef + +-- Two real arguments, so the early run splits it and the thunk ends up +-- inside the worker. The body is deliberately over the call-site inline +-- budget, so the call shape survives to be measured — built from +-- read/write pairs, which unlike @modify@ allocate no per-iteration +-- lambda that would drown the thunk-allocation delta this bench exposes. +step :: forall r. Int -> STRef r Int -> ST r Unit +step k ref = do + a <- STRef.read ref + _ <- STRef.write (a + k) ref + b <- STRef.read ref + _ <- STRef.write (b + k) ref + c <- STRef.read ref + _ <- STRef.write (c + k) ref + d <- STRef.read ref + _ <- STRef.write (d + k) ref + e <- STRef.read ref + _ <- STRef.write (e + k) ref + pure unit + +go :: forall r. Int -> STRef r Int -> ST r Int +go i ref = do + step 3 ref + if i <= 1 then STRef.read ref else go (i - 1) ref + +run :: Int -> Int +run n = ST.run do + ref <- STRef.new 0 + go n ref diff --git a/test/ps/src/Golden/EffectWorkerThunk/Test.purs b/test/ps/src/Golden/EffectWorkerThunk/Test.purs new file mode 100644 index 00000000..292f0e45 --- /dev/null +++ b/test/ps/src/Golden/EffectWorkerThunk/Test.purs @@ -0,0 +1,58 @@ +-- | Exercises absorbing the magic-do thunk into an early-split effect +-- | worker (issue #265). An effect action of two or more real arguments +-- | is saturated before magic-do runs, so the uncurry split fires at the +-- | real arity and the worker's body is later rewritten into a thunk: +-- | every statement site then reads `f$w(a, b)()`, one closure and one +-- | extra call. `report` shows the plain win (its wrapper is dead, so +-- | only the worker survives). `tally` is also passed as a value, so its +-- | curried wrapper stays alive and must grow a parameter to keep a +-- | partial application evaluating to a closure. `deferred` pins the +-- | disqualified shape: its worker call is bound as an action value and +-- | run later, a site that extending the arity would leave +-- | under-applied, so the binding keeps the call-then-force shape. +module Golden.EffectWorkerThunk.Test where + +import Prelude + +import Effect (Effect) +import Effect.Console (log, logShow) + +report :: String -> Int -> Effect Unit +report tag n = do + log tag + logShow n + log "-" + +tally :: String -> Int -> Effect Unit +tally tag n = do + log tag + logShow (n * 2) + log "=" + +deferred :: String -> Int -> Effect Unit +deferred tag n = do + log tag + logShow (n + 100) + log "+" + +runWith :: (String -> Int -> Effect Unit) -> Effect Unit +runWith f = do + f "via" 3 + log "ran" + +main :: Effect Unit +main = do + report "a" 1 + report "b" 2 + tally "c" 3 + runWith tally + let step a b = do + log a + logShow (b * 10) + log "*" + step "e" 5 + step "f" 6 + let held = deferred "d" 4 + log "before held" + held + held