From a5469eac32de579b8160cbc4ca9ac0b34c35c466 Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 29 Jul 2026 20:07:52 +0200 Subject: [PATCH 1/3] test(golden): pin the call-then-force shape of a multi-argument effect statement (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Effect action of two or more real arguments is saturated at its real arity before magic-do runs, so the uncurrying split fires there and the thunk magic-do builds ends up inside an already n-ary worker. Every fully applied statement site therefore compiles to a worker call that allocates the thunk plus the run that forces it. Golden.EffectWorkerThunk pins that shape from four sides: a plain case whose wrapper is dead, one whose wrapper survives as a value, a let-bound local worker, and an action bound to a name and run later — the shape whose arity may not be widened. The eval oracle pins the runtime output. --- .../Golden.EffectWorkerThunk.Test/corefn.json | 1 + .../eval/.gitignore | 1 + .../eval/golden.txt | 26 ++ .../Golden.EffectWorkerThunk.Test/golden.ir | 437 ++++++++++++++++++ .../Golden.EffectWorkerThunk.Test/golden.lua | 57 +++ .../ps/src/Golden/EffectWorkerThunk/Test.purs | 58 +++ 6 files changed, 580 insertions(+) create mode 100644 test/ps/output/Golden.EffectWorkerThunk.Test/corefn.json create mode 100644 test/ps/output/Golden.EffectWorkerThunk.Test/eval/.gitignore create mode 100644 test/ps/output/Golden.EffectWorkerThunk.Test/eval/golden.txt create mode 100644 test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir create mode 100644 test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua create mode 100644 test/ps/src/Golden/EffectWorkerThunk/Test.purs 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..7cf1e1b4 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir @@ -0,0 +1,437 @@ +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" ) ] ) + ( 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 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" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "tally$p1" ) ) :| + [ Ref Nothing ( Local ( Name "tally$p2" ) ) ] + ) + ) + ) + ), 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" ) ] ) + ( 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" ) ) + ) + ( 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" ) :| [] ) + ( 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" ) ) ] + ) + ) + ) + ), + ( 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 + ( 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 + ( 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 + ( 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 + ( 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" ) ] ) + ( 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 "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 + ( 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 + ( 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..26796600 --- /dev/null +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua @@ -0,0 +1,57 @@ +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) + return function() + local _ = Effect_Console_log(tag)() + local _ = Effect_Console_log(Data_Show_showIntImpl(n * 2))() + return Effect_Console_log("=")() + end +end +M.Golden_EffectWorkerThunk_Test_tally = function(tally_S_p1) + return function(tally_S_p2) + return Golden_EffectWorkerThunk_Test_tally_S_w(tally_S_p1, tally_S_p2) + 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) + 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_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) + return function() + local _ = Effect_Console_log(a_S_0)() + local _ = Effect_Console_log(Data_Show_showIntImpl(b_S_0 * 10))() + return Effect_Console_log("*")() + end + 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/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 From 79209b1489e2630887d22ac4c19b4a4454dc0ae9 Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 29 Jul 2026 20:08:03 +0200 Subject: [PATCH 2/3] bench(effect): add the two-argument effect-step macrobenchmark (#265) Bench.EffectStep drives a hot ST loop through a unary effect action, the case the late uncurry run splits. Bench.EffectStep2 is its two-argument sibling: with two real arguments the spine is already saturated when the early uncurry run measures it, so the split fires at the real arity and the thunk sits inside the worker, out of the late run reach. Every iteration pays the thunk allocation plus the call that forces it, which the committed counter oracles record as two NYI: bytecode FNEW trace aborts and an interpreted driver. --- bench/goldens/fnew_Bench.EffectStep2.txt | 19 ++++++++++ bench/goldens/tnew_Bench.EffectStep2.txt | 8 ++++ bench/goldens/trace_effect_step2.txt | 22 +++++++++++ bench/macro/effect_step2.lua | 19 ++++++++++ test/ps/src/Bench/EffectStep2.purs | 47 ++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 bench/goldens/fnew_Bench.EffectStep2.txt create mode 100644 bench/goldens/tnew_Bench.EffectStep2.txt create mode 100644 bench/goldens/trace_effect_step2.txt create mode 100644 bench/macro/effect_step2.lua create mode 100644 test/ps/src/Bench/EffectStep2.purs diff --git a/bench/goldens/fnew_Bench.EffectStep2.txt b/bench/goldens/fnew_Bench.EffectStep2.txt new file mode 100644 index 00000000..45734398 --- /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:29 + Bench.EffectStep2.lua:40 + Bench.EffectStep2.lua:43 + Bench.EffectStep2.lua:49 + Bench.EffectStep2.lua:56 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_step2.txt b/bench/goldens/trace_effect_step2.txt new file mode 100644 index 00000000..ad22c2bc --- /dev/null +++ b/bench/goldens/trace_effect_step2.txt @@ -0,0 +1,22 @@ +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:29 -- NYI: bytecode FNEW + Bench.EffectStep2.lua:40 -- NYI: bytecode FNEW + Bench.EffectStep2.lua:56 -- 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:17 IFUNCF + Bench.EffectStep2.lua:32 IFUNCF + Bench.EffectStep2.lua:33 IFUNCF + Bench.EffectStep2.lua:8 JFUNCF + Bench.EffectStep2.lua:9 IFUNCF + Bench.EffectStep2.lua:9 JFUNCF +counts: aborts=6 compiled=3 blacklisted=7 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/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 From c724f89f3e9158ee484d9fc91333abae9bb66aa5 Mon Sep 17 00:00:00 2001 From: Yura Lazarev Date: Wed, 29 Jul 2026 20:11:10 +0200 Subject: [PATCH 3/3] feat(ir): absorb the magic-do thunk into early-split effect workers (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Effect/ST action of two or more real arguments is saturated at that arity before magic-do runs, so the uncurrying worker/wrapper split fires at the real arity and magic-do afterwards rewrites the worker's body into the nullary thunk an Effect value is. Every fully applied statement site then allocates that thunk and immediately forces it, two Lua calls with a closure in between. The late uncurry run cannot repair it: it splits manifest lambda chains, and the thunk is inside a worker that is already n-ary. absorbEffectThunk widens such a worker in place — the thunk's parameter joins its parameter list, its body becomes the worker's body — and rewrites every forced site w(a...)(run) to the single call w(a..., run), still an effect run by its trailing marker. Each wrapper delegating to the worker grows one parameter, which the delegate passes on, so a partial application still evaluates to a closure. The precondition is that every reference is a forced site or such a delegate, with at least one forced site: the Lua backend drops the worker's trailing unused parameter run, so any other reference the wider arity leaves under-applied would become a saturated Lua call and run the effect at construction time. Absorbing the marker also makes a recursive driver's self-call a genuine tail call, so the native-loop lowering turns it into a Lua while loop. Bench.EffectStep2 gains 1.22x under PUC Lua 5.1 and 1.28x under LuaJIT; Bench.EffectStep, whose two-argument driver is the same case, gains 1.10x/1.14x, and both trace reports lose their thunk-allocation aborts with the driver ending as a compiled ILOOP. The pass runs last, after the late uncurry run and the dce that follows it: nothing later moves a call, so the reference census is final, and the wrappers whose sites all went to their workers are already gone. --- bench/goldens/fnew_Bench.EffectStep.txt | 6 +- bench/goldens/fnew_Bench.EffectStep2.txt | 8 +- bench/goldens/trace_effect_step.txt | 8 +- bench/goldens/trace_effect_step2.txt | 10 +- ...60729_200000_unisay_absorb_effect_thunk.md | 83 +++++ .../Backend/IR/AbsorbEffectThunk.hs | 304 ++++++++++++++++++ .../PureScript/Backend/IR/Optimizer.hs | 13 + lib/Language/PureScript/Backend/IR/Uncurry.hs | 12 + pslua.cabal | 2 + .../Backend/IR/AbsorbEffectThunk/Spec.hs | 205 ++++++++++++ test/Main.hs | 2 + .../Golden.EffectWorkerThunk.Test/golden.ir | 292 +++++++++-------- .../Golden.EffectWorkerThunk.Test/golden.lua | 40 ++- .../Golden.NativeLoopsGuard.Test/golden.ir | 220 ++++++------- .../Golden.NativeLoopsGuard.Test/golden.lua | 14 +- 15 files changed, 913 insertions(+), 306 deletions(-) create mode 100644 changelog.d/20260729_200000_unisay_absorb_effect_thunk.md create mode 100644 lib/Language/PureScript/Backend/IR/AbsorbEffectThunk.hs create mode 100644 test/Language/PureScript/Backend/IR/AbsorbEffectThunk/Spec.hs 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 index 45734398..71684b9f 100644 --- a/bench/goldens/fnew_Bench.EffectStep2.txt +++ b/bench/goldens/fnew_Bench.EffectStep2.txt @@ -12,8 +12,8 @@ function-body FNEW sites: Bench.EffectStep2.lua:9 Bench.EffectStep2.lua:11 Bench.EffectStep2.lua:11 - Bench.EffectStep2.lua:29 - Bench.EffectStep2.lua:40 + Bench.EffectStep2.lua:44 Bench.EffectStep2.lua:43 - Bench.EffectStep2.lua:49 - Bench.EffectStep2.lua:56 + Bench.EffectStep2.lua:52 + Bench.EffectStep2.lua:51 + Bench.EffectStep2.lua:59 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 index ad22c2bc..be91fc21 100644 --- a/bench/goldens/trace_effect_step2.txt +++ b/bench/goldens/trace_effect_step2.txt @@ -3,9 +3,7 @@ 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:29 -- NYI: bytecode FNEW - Bench.EffectStep2.lua:40 -- NYI: bytecode FNEW - Bench.EffectStep2.lua:56 -- 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): @@ -13,10 +11,8 @@ bytecode end state (J*=compiled, I*=blacklisted): Bench.EffectStep2.lua:11 IFUNCF Bench.EffectStep2.lua:11 JFUNCF Bench.EffectStep2.lua:16 IFUNCF - Bench.EffectStep2.lua:17 IFUNCF - Bench.EffectStep2.lua:32 IFUNCF - Bench.EffectStep2.lua:33 IFUNCF + Bench.EffectStep2.lua:30 ILOOP Bench.EffectStep2.lua:8 JFUNCF Bench.EffectStep2.lua:9 IFUNCF Bench.EffectStep2.lua:9 JFUNCF -counts: aborts=6 compiled=3 blacklisted=7 +counts: aborts=4 compiled=3 blacklisted=5 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/golden.ir b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir index 7cf1e1b4..ba576276 100644 --- a/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.ir @@ -22,43 +22,41 @@ UberModule ( QName { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "tally$w" }, AbsN Nothing - ( ParamNamed Nothing ( Name "tag" ) :| [ ParamNamed Nothing ( Name "n" ) ] ) - ( AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Let Nothing - ( Standalone + ( 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" ) ) ) - ( 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 "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" ) ) :| [] ) ) - ( 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 @@ -68,13 +66,19 @@ UberModule ( ParamNamed Nothing ( Name "tally$p1" ) :| [] ) ( AbsN Nothing ( ParamNamed Nothing ( Name "tally$p2" ) :| [] ) - ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) - ) - ( Ref Nothing - ( Local ( Name "tally$p1" ) ) :| - [ Ref Nothing ( Local ( 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" ) ) + ] + ) ) ) ) @@ -111,40 +115,38 @@ UberModule ( QName { qnameModuleName = ModuleName "Golden.EffectWorkerThunk.Test", qnameName = Name "report$w" }, AbsN Nothing - ( ParamNamed Nothing ( Name "tag" ) :| [ ParamNamed Nothing ( Name "n" ) ] ) - ( AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Let Nothing - ( Standalone + ( 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" ) ) ) - ( 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 "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" ) ) :| [] ) ) - ( 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 @@ -197,13 +199,19 @@ UberModule ( ParamNamed Nothing ( Name "report$p1$0" ) :| [] ) ( AbsN Nothing ( ParamNamed Nothing ( Name "report$p2$0" ) :| [] ) - ( 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" ) ) ] + ( 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" ) ) + ] + ) ) ) ) @@ -234,32 +242,35 @@ UberModule ( Let Nothing ( Standalone ( Nothing, Name "_", AppN Nothing - ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) - ) - ( LiteralString Nothing "a" :| [ LiteralInt Nothing 1 ] ) + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) + ) + ( LiteralString Nothing "a" :| + [ LiteralInt Nothing 1, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ) :| [ Standalone ( Nothing, Name "_", AppN Nothing - ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) - ) - ( LiteralString Nothing "b" :| [ LiteralInt Nothing 2 ] ) + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "report$w" ) ) + ) + ( LiteralString Nothing "b" :| + [ LiteralInt Nothing 2, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ), Standalone ( Nothing, Name "_", AppN Nothing - ( AppN Nothing - ( Ref Nothing - ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) - ) - ( LiteralString Nothing "c" :| [ LiteralInt Nothing 3 ] ) + ( Ref Nothing + ( Imported ( ModuleName "Golden.EffectWorkerThunk.Test" ) ( Name "tally$w" ) ) + ) + ( LiteralString Nothing "c" :| + [ LiteralInt Nothing 3, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) - ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) ), Standalone ( Nothing, Name "_", AppN Nothing ( AbsN Nothing @@ -267,17 +278,16 @@ UberModule ( Let Nothing ( Standalone ( Nothing, Name "_", AppN Nothing - ( AppN Nothing - ( Ref Nothing - ( Imported - ( ModuleName "Golden.EffectWorkerThunk.Test" ) - ( Name "tally$w" ) - ) + ( Ref Nothing + ( Imported + ( ModuleName "Golden.EffectWorkerThunk.Test" ) + ( Name "tally$w" ) ) - ( LiteralString Nothing "via" :| [ LiteralInt Nothing 3 ] ) ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( LiteralString Nothing "via" :| + [ LiteralInt Nothing 3, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) ) :| [] ) @@ -300,54 +310,52 @@ UberModule ( Let Nothing ( Standalone ( Nothing, Name "step$0$w", AbsN Nothing - ( ParamNamed Nothing ( Name "a$0" ) :| [ ParamNamed Nothing ( Name "b$0" ) ] ) - ( AbsN Nothing - ( ParamUnused Nothing :| [] ) - ( Let Nothing - ( Standalone + ( 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" ) ) ) - ( 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 ) :| [] - ) :| [] + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) ) - ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( PrimBinOp Nothing PrimMul + ( Ref Nothing ( Local ( Name "b$0" ) ) ) + ( LiteralInt Nothing 10 ) :| [] + ) :| [] ) ) - ] - ) - ( AppN Nothing - ( AppN Nothing ( Ref Nothing - ( Imported ( ModuleName "Effect.Console" ) ( Name "log" ) ) + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) - ( LiteralString Nothing "*" :| [] ) - ) - ( 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" ) ) :| [] ) ) ) @@ -358,22 +366,20 @@ UberModule ( Let Nothing ( Standalone ( Nothing, Name "_", AppN Nothing - ( AppN Nothing - ( Ref Nothing ( Local ( Name "step$0$w" ) ) ) - ( LiteralString Nothing "e" :| [ LiteralInt Nothing 5 ] ) - ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( 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 - ( AppN Nothing - ( Ref Nothing ( Local ( Name "step$0$w" ) ) ) - ( LiteralString Nothing "f" :| [ LiteralInt Nothing 6 ] ) - ) - ( Ref Nothing - ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] + ( Ref Nothing ( Local ( Name "step$0$w" ) ) ) + ( LiteralString Nothing "f" :| + [ LiteralInt Nothing 6, Ref Nothing + ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) + ] ) ) ] diff --git a/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua index 26796600..3ffa4cf3 100644 --- a/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua +++ b/test/ps/output/Golden.EffectWorkerThunk.Test/golden.lua @@ -2,15 +2,15 @@ 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) - return function() - local _ = Effect_Console_log(tag)() - local _ = Effect_Console_log(Data_Show_showIntImpl(n * 2))() - return Effect_Console_log("=")() - end + 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 Golden_EffectWorkerThunk_Test_tally_S_w(tally_S_p1, 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) @@ -20,11 +20,9 @@ M.Golden_EffectWorkerThunk_Test_runWith = function(f) end end 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 + 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() @@ -34,22 +32,20 @@ local Golden_EffectWorkerThunk_Test_deferred_S_w = function(tag, n) 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 _ = 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)() + 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) - return function() - local _ = Effect_Console_log(a_S_0)() - local _ = Effect_Console_log(Data_Show_showIntImpl(b_S_0 * 10))() - return Effect_Console_log("*")() - end + 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 _ = 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() 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)()