From ecdc42b307a8237f95c30e75d9e0268966ec41aa Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Thu, 22 Jun 2023 16:34:06 +0300 Subject: [PATCH 01/14] Semantics --- src/PureScript/Backend/Optimizer/Convert.purs | 2 +- .../Backend/Optimizer/Semantics.purs | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/PureScript/Backend/Optimizer/Convert.purs b/src/PureScript/Backend/Optimizer/Convert.purs index 04f68146..6cfaed93 100644 --- a/src/PureScript/Backend/Optimizer/Convert.purs +++ b/src/PureScript/Backend/Optimizer/Convert.purs @@ -234,7 +234,7 @@ toBackendTopLevelBindingGroup env = case _ of toTopLevelBackendBinding :: Array (Qualified Ident) -> ConvertEnv -> Binding Ann -> Accum ConvertEnv (Tuple Ident (WithDeps NeutralExpr)) toTopLevelBackendBinding group env (Binding _ ident cfn) = do - let evalEnv = Env { currentModule: env.currentModule, evalExtern: makeExternEval env, locals: [], directives: env.directives } + let evalEnv = Env { currentModule: env.currentModule, evalExtern: makeExternEval env, locals: [], punt: Set.empty, directives: env.directives } let backendExpr = toBackendExpr cfn env let Tuple impl expr' = toExternImpl env group (optimize (getCtx env) evalEnv (Qualified (Just env.currentModule) ident) env.rewriteLimit backendExpr) { accum: env diff --git a/src/PureScript/Backend/Optimizer/Semantics.purs b/src/PureScript/Backend/Optimizer/Semantics.purs index 24f9d360..5062c72f 100644 --- a/src/PureScript/Backend/Optimizer/Semantics.purs +++ b/src/PureScript/Backend/Optimizer/Semantics.purs @@ -48,6 +48,7 @@ data BackendSemantics | NeutLocal (Maybe Ident) Level | NeutVar (Qualified Ident) | NeutStop (Qualified Ident) + | RecurseWithRecklessAbandon (Qualified Ident) | NeutData (Qualified Ident) ConstructorType ProperName Ident (Array (Tuple String BackendSemantics)) | NeutCtorDef (Qualified Ident) ConstructorType ProperName Ident (Array String) | NeutApp BackendSemantics (Spine BackendSemantics) @@ -86,6 +87,7 @@ data BackendRewrite | RewriteLetAssoc (Array (LetBindingAssoc BackendExpr)) BackendExpr | RewriteEffectBindAssoc (Array (EffectBindingAssoc BackendExpr)) BackendExpr | RewriteStop (Qualified Ident) + | RewriteRecurse (Qualified Ident) | RewriteUnpackOp (Maybe Ident) Level UnpackOp BackendExpr | RewriteDistBranchesLet (Maybe Ident) Level (NonEmptyArray (Pair BackendExpr)) BackendExpr BackendExpr | RewriteDistBranchesOp (NonEmptyArray (Pair BackendExpr)) BackendExpr DistOp @@ -164,6 +166,7 @@ newtype Env = Env , evalExtern :: Env -> Qualified Ident -> Array ExternSpine -> Maybe BackendSemantics , locals :: Array (LocalBinding BackendSemantics) , directives :: InlineDirectiveMap + , punt :: Set.Set (Qualified Ident) } derive instance Newtype Env _ @@ -195,13 +198,18 @@ addStop (Env env) ref acc = Env env env.directives } +puntMe :: Env -> Array (Qualified Ident) -> Env +puntMe (Env env) quals = Env env + { punt = Set.union env.punt (Set.fromFoldable quals) + } class Eval f where eval :: Env -> f -> BackendSemantics instance Eval f => Eval (BackendSyntax f) where - eval env = case _ of - Var qual -> - evalExtern env qual [] + eval env@(Env { punt }) = case _ of + Var qual + | qual `Set.member` punt -> RecurseWithRecklessAbandon qual + | otherwise -> evalExtern env qual [] Local ident lvl -> case lookupLocal env lvl of Just (One sem) -> sem @@ -279,6 +287,7 @@ instance Eval BackendExpr where go env = case _ of ExprRewrite _ rewrite -> case rewrite of + RewriteRecurse ident -> eval env (Var ident :: BackendSyntax BackendExpr) RewriteInline _ _ binding body -> go (bindLocal env (One (eval env binding))) body RewriteUncurry ident _ args binding body -> @@ -846,7 +855,7 @@ evalExternFromImpl env@(Env e) qual (Tuple analysis impl) spine = case spine of Just InlineNever -> Just $ NeutStop qual Just InlineAlways -> - Just $ eval (envForGroup env ref InlineRef group) expr + Just $ eval (puntMe env group) expr Just (InlineArity _) -> Nothing _ -> @@ -1056,6 +1065,7 @@ quote = go foldr (buildBranchCond ctx) (quote ctx <<< force $ def) branches' -- Non-block constructors + RecurseWithRecklessAbandon ident -> ExprRewrite (withRewrite (analyzeDefault ctx (Var ident))) $ RewriteRecurse ident SemExtern _ _ sem -> go ctx (force sem) SemLam ident k -> do @@ -1529,6 +1539,8 @@ freeze init = Tuple (analysisOf init) (go init) NeutralExpr $ Let ident level (NeutralExpr (Abs args (go binding))) (go body) RewriteStop qual -> NeutralExpr $ Var qual + RewriteRecurse qual -> + NeutralExpr $ Var qual RewriteLetAssoc bindings body -> case NonEmptyArray.fromArray bindings of Just bindings' -> do From 258d1ad1951d2dc1bd1584632e8fa3eb36198665 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Thu, 22 Jun 2023 16:36:22 +0300 Subject: [PATCH 02/14] Adds test --- .../Snapshot.RecursionInlined01.js | 24 +++++++++++++++++++ .../Snapshot.RecursionInlined01.purs | 14 +++++++++++ 2 files changed, 38 insertions(+) create mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js create mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlined01.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js new file mode 100644 index 00000000..7549c484 --- /dev/null +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js @@ -0,0 +1,24 @@ +// @inline Snapshot.RecursionInlined01.append always +import * as $runtime from "../runtime.js"; +const $List = (tag, _1, _2) => ({tag, _1, _2}); +const Nil = /* #__PURE__ */ $List("Nil"); +const Cons = value0 => value1 => $List("Cons", value0, value1); +const append = v => v1 => { + if (v.tag === "Nil") { return v1; } + if (v.tag === "Cons") { return $List("Cons", v._1, append(v._2)(v1)); } + $runtime.fail(); +}; +const main = /* #__PURE__ */ $List( + "Cons", + "a", + /* #__PURE__ */ $List( + "Cons", + "b", + /* #__PURE__ */ $List( + "Cons", + "c", + /* #__PURE__ */ $List("Cons", "d", /* #__PURE__ */ $List("Cons", "e", /* #__PURE__ */ $List("Cons", "f", /* #__PURE__ */ $List("Cons", "g", Nil)))) + ) + ) +); +export {$List, Cons, Nil, append, main}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs new file mode 100644 index 00000000..ff3a3297 --- /dev/null +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs @@ -0,0 +1,14 @@ +-- @inline Snapshot.RecursionInlined01.append always +module Snapshot.RecursionInlined01 where + +data List a = Nil | Cons a (List a) +infixr 5 Cons as : + +append :: forall a. List a -> List a -> List a +append Nil ys = ys +append (Cons x xs) ys = Cons x (append xs ys) + +infixr 4 append as <> + +main :: List String +main = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : Nil) <> Nil \ No newline at end of file From a379b41e3435597e92cbd8b089b1c477d791deb5 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Thu, 22 Jun 2023 18:31:22 +0300 Subject: [PATCH 03/14] Adds a couple more cases --- src/PureScript/Backend/Optimizer/Semantics.purs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PureScript/Backend/Optimizer/Semantics.purs b/src/PureScript/Backend/Optimizer/Semantics.purs index 5062c72f..3c834e5a 100644 --- a/src/PureScript/Backend/Optimizer/Semantics.purs +++ b/src/PureScript/Backend/Optimizer/Semantics.purs @@ -861,9 +861,9 @@ evalExternFromImpl env@(Env e) qual (Tuple analysis impl) spine = case spine of _ -> case expr of NeutralExpr (Lit lit) | shouldInlineExternLiteral lit -> - Just $ eval (envForGroup env ref InlineRef group) expr + Just $ eval (puntMe env group) expr _ | shouldInlineExternReference qual analysis expr -> - Just $ eval (envForGroup env ref InlineRef group) expr + Just $ eval (puntMe env group) expr _ -> Nothing ExternCtor _ ct ty tag [] -> From 504b08d625820ff6b90ade2864510dc1add8f9a6 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Sun, 25 Jun 2023 10:57:12 +0300 Subject: [PATCH 04/14] Renames main to test1 --- backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js | 4 ++-- backend-es/test/snapshots/Snapshot.RecursionInlined01.purs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js index 7549c484..344e9e68 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js @@ -8,7 +8,7 @@ const append = v => v1 => { if (v.tag === "Cons") { return $List("Cons", v._1, append(v._2)(v1)); } $runtime.fail(); }; -const main = /* #__PURE__ */ $List( +const test1 = /* #__PURE__ */ $List( "Cons", "a", /* #__PURE__ */ $List( @@ -21,4 +21,4 @@ const main = /* #__PURE__ */ $List( ) ) ); -export {$List, Cons, Nil, append, main}; +export {$List, Cons, Nil, append, test1}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs index ff3a3297..8fb8624d 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs @@ -10,5 +10,5 @@ append (Cons x xs) ys = Cons x (append xs ys) infixr 4 append as <> -main :: List String -main = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : Nil) <> Nil \ No newline at end of file +test1 :: List String +test1 = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : Nil) <> Nil \ No newline at end of file From c10bc8e834d9274d38a04958f2dc86d5fa9a2447 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 11:22:23 +0300 Subject: [PATCH 05/14] Adds first guard --- src/PureScript/Backend/Optimizer/Convert.purs | 2 +- .../Backend/Optimizer/Semantics.purs | 29 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/PureScript/Backend/Optimizer/Convert.purs b/src/PureScript/Backend/Optimizer/Convert.purs index 6cfaed93..b21c56a5 100644 --- a/src/PureScript/Backend/Optimizer/Convert.purs +++ b/src/PureScript/Backend/Optimizer/Convert.purs @@ -234,7 +234,7 @@ toBackendTopLevelBindingGroup env = case _ of toTopLevelBackendBinding :: Array (Qualified Ident) -> ConvertEnv -> Binding Ann -> Accum ConvertEnv (Tuple Ident (WithDeps NeutralExpr)) toTopLevelBackendBinding group env (Binding _ ident cfn) = do - let evalEnv = Env { currentModule: env.currentModule, evalExtern: makeExternEval env, locals: [], punt: Set.empty, directives: env.directives } + let evalEnv = Env { currentModule: env.currentModule, evalExtern: makeExternEval env, locals: [], punt: Set.empty, directives: env.directives, blockNextRecursion: false } let backendExpr = toBackendExpr cfn env let Tuple impl expr' = toExternImpl env group (optimize (getCtx env) evalEnv (Qualified (Just env.currentModule) ident) env.rewriteLimit backendExpr) { accum: env diff --git a/src/PureScript/Backend/Optimizer/Semantics.purs b/src/PureScript/Backend/Optimizer/Semantics.purs index 3c834e5a..94475803 100644 --- a/src/PureScript/Backend/Optimizer/Semantics.purs +++ b/src/PureScript/Backend/Optimizer/Semantics.purs @@ -167,6 +167,7 @@ newtype Env = Env , locals :: Array (LocalBinding BackendSemantics) , directives :: InlineDirectiveMap , punt :: Set.Set (Qualified Ident) + , blockNextRecursion :: Boolean } derive instance Newtype Env _ @@ -186,6 +187,15 @@ insertDirective ref acc dir = Map.alter Just $ Map.singleton acc dir ref +recursable :: Array BackendSemantics -> Boolean +recursable = go + where + go arr + | Just { head, tail } <- Array.uncons arr = case head of + NeutData _ _ _ _ _ -> true && go tail + _ -> false + | otherwise = true + addStop :: Env -> EvalRef -> InlineAccessor -> Env addStop (Env env) ref acc = Env env { directives = Map.alter @@ -202,11 +212,12 @@ puntMe :: Env -> Array (Qualified Ident) -> Env puntMe (Env env) quals = Env env { punt = Set.union env.punt (Set.fromFoldable quals) } + class Eval f where eval :: Env -> f -> BackendSemantics instance Eval f => Eval (BackendSyntax f) where - eval env@(Env { punt }) = case _ of + eval env@(Env envx@{ punt }) = case _ of Var qual | qual `Set.member` punt -> RecurseWithRecklessAbandon qual | otherwise -> evalExtern env qual [] @@ -217,8 +228,11 @@ instance Eval f => Eval (BackendSyntax f) where force sem _ -> unsafeCrashWith $ "Unbound local at level " <> show (unwrap lvl) - App hd tl -> - evalApp env (eval env hd) (NonEmptyArray.toArray (eval env <$> tl)) + App hd tl -> do + let tailEvaled = NonEmptyArray.toArray (eval env <$> tl) + let canRecurse = recursable tailEvaled + let headEvaled = eval (Env envx { blockNextRecursion = not canRecurse }) hd + evalApp env headEvaled tailEvaled UncurriedApp hd tl -> evalUncurriedApp env (eval env hd) (eval env <$> tl) UncurriedAbs idents body -> do @@ -282,12 +296,12 @@ instance Eval f => Eval (BackendSyntax f) where guardFailOver snd (map (eval env) <$> fields) $ NeutData qual ct ty tag instance Eval BackendExpr where - eval = go + eval (Env e@{ blockNextRecursion }) = go (Env e { blockNextRecursion = false }) where go env = case _ of ExprRewrite _ rewrite -> case rewrite of - RewriteRecurse ident -> eval env (Var ident :: BackendSyntax BackendExpr) + RewriteRecurse ident -> if blockNextRecursion then mkSemExtern ident [] else eval env (Var ident :: BackendSyntax BackendExpr) RewriteInline _ _ binding body -> go (bindLocal env (One (eval env binding))) body RewriteUncurry ident _ args binding body -> @@ -835,9 +849,12 @@ primOpOrdNot = case _ of OpGt -> OpLte OpGte -> OpLt +mkSemExtern :: Qualified Ident -> Array ExternSpine -> BackendSemantics +mkSemExtern qual spine = SemExtern qual spine (defer \_ -> neutralSpine (NeutVar qual) spine) + evalExtern :: Env -> Qualified Ident -> Array ExternSpine -> BackendSemantics evalExtern env@(Env e) qual spine = case e.evalExtern env qual spine of - Nothing -> SemExtern qual spine (defer \_ -> neutralSpine (NeutVar qual) spine) + Nothing -> mkSemExtern qual spine Just sem -> sem envForGroup :: Env -> EvalRef -> InlineAccessor -> Array (Qualified Ident) -> Env From 30b82a5b480fea81efe18e92a7875f20a87cedfa Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 11:26:05 +0300 Subject: [PATCH 06/14] Adds test --- .../Snapshot.RecursionInlined02.js | 44 +++++++++++++++++++ .../Snapshot.RecursionInlined02.purs | 14 ++++++ 2 files changed, 58 insertions(+) create mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js create mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlined02.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js new file mode 100644 index 00000000..d5871c39 --- /dev/null +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js @@ -0,0 +1,44 @@ +// @inline Snapshot.RecursionInlined02.append always +import * as $runtime from "../runtime.js"; +const $List = (tag, _1, _2) => ({tag, _1, _2}); +const Nil = /* #__PURE__ */ $List("Nil"); +const Cons = value0 => value1 => $List("Cons", value0, value1); +const append = v => v1 => { + if (v.tag === "Nil") { return v1; } + if (v.tag === "Cons") { return $List("Cons", v._1, append(v._2)(v1)); } + $runtime.fail(); +}; +const test1 = z => $List( + "Cons", + "a", + $List( + "Cons", + "b", + $List( + "Cons", + "c", + $List( + "Cons", + "d", + $List( + "Cons", + "e", + $List( + "Cons", + "f", + $List( + "Cons", + "g", + (() => { + if (z.tag === "Nil") { return Nil; } + if (z.tag === "Cons") { return $List("Cons", z._1, append(z._2)(Nil)); } + $runtime.fail(); + })() + ) + ) + ) + ) + ) + ) +); +export {$List, Cons, Nil, append, test1}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs new file mode 100644 index 00000000..8478a151 --- /dev/null +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs @@ -0,0 +1,14 @@ +-- @inline Snapshot.RecursionInlined02.append always +module Snapshot.RecursionInlined02 where + +data List a = Nil | Cons a (List a) +infixr 5 Cons as : + +append :: forall a. List a -> List a -> List a +append Nil ys = ys +append (Cons x xs) ys = Cons x (append xs ys) + +infixr 4 append as <> + +test1 :: List String -> List String +test1 z = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : z) <> Nil \ No newline at end of file From daf524124c161128ab5060fbc02cceff8a3a089f Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 11:42:56 +0300 Subject: [PATCH 07/14] Removes spurious true --- src/PureScript/Backend/Optimizer/Semantics.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PureScript/Backend/Optimizer/Semantics.purs b/src/PureScript/Backend/Optimizer/Semantics.purs index 94475803..133bc707 100644 --- a/src/PureScript/Backend/Optimizer/Semantics.purs +++ b/src/PureScript/Backend/Optimizer/Semantics.purs @@ -192,7 +192,7 @@ recursable = go where go arr | Just { head, tail } <- Array.uncons arr = case head of - NeutData _ _ _ _ _ -> true && go tail + NeutData _ _ _ _ _ -> go tail _ -> false | otherwise = true From 4e2603482728a02dc6394519dddc9ddf234600da Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 12:01:00 +0300 Subject: [PATCH 08/14] Shrinks to one test --- .../Snapshot.RecursionInlined01.js | 19 +++++++- .../Snapshot.RecursionInlined02.js | 44 ------------------- .../Snapshot.RecursionInlined01.purs | 4 +- .../Snapshot.RecursionInlined02.purs | 14 ------ 4 files changed, 21 insertions(+), 60 deletions(-) delete mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js delete mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlined02.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js index 344e9e68..1a780ffd 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined01.js @@ -21,4 +21,21 @@ const test1 = /* #__PURE__ */ $List( ) ) ); -export {$List, Cons, Nil, append, test1}; +const test2 = z => $List( + "Cons", + "a", + $List( + "Cons", + "b", + $List( + "Cons", + "c", + (() => { + if (z.tag === "Nil") { return $List("Cons", "d", $List("Cons", "e", $List("Cons", "f", $List("Cons", "g", Nil)))); } + if (z.tag === "Cons") { return $List("Cons", z._1, append(z._2)($List("Cons", "d", $List("Cons", "e", $List("Cons", "f", $List("Cons", "g", Nil)))))); } + $runtime.fail(); + })() + ) + ) +); +export {$List, Cons, Nil, append, test1, test2}; diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js deleted file mode 100644 index d5871c39..00000000 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js +++ /dev/null @@ -1,44 +0,0 @@ -// @inline Snapshot.RecursionInlined02.append always -import * as $runtime from "../runtime.js"; -const $List = (tag, _1, _2) => ({tag, _1, _2}); -const Nil = /* #__PURE__ */ $List("Nil"); -const Cons = value0 => value1 => $List("Cons", value0, value1); -const append = v => v1 => { - if (v.tag === "Nil") { return v1; } - if (v.tag === "Cons") { return $List("Cons", v._1, append(v._2)(v1)); } - $runtime.fail(); -}; -const test1 = z => $List( - "Cons", - "a", - $List( - "Cons", - "b", - $List( - "Cons", - "c", - $List( - "Cons", - "d", - $List( - "Cons", - "e", - $List( - "Cons", - "f", - $List( - "Cons", - "g", - (() => { - if (z.tag === "Nil") { return Nil; } - if (z.tag === "Cons") { return $List("Cons", z._1, append(z._2)(Nil)); } - $runtime.fail(); - })() - ) - ) - ) - ) - ) - ) -); -export {$List, Cons, Nil, append, test1}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs index 8fb8624d..38657386 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined01.purs @@ -11,4 +11,6 @@ append (Cons x xs) ys = Cons x (append xs ys) infixr 4 append as <> test1 :: List String -test1 = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : Nil) <> Nil \ No newline at end of file +test1 = ("a" : "b" : "c" : Nil) <> ("d" : "e" : "f" : "g" : Nil) +test2 :: List String -> List String +test2 z = ("a" : "b" : "c" : z) <> ("d" : "e" : "f" : "g" : Nil) \ No newline at end of file diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs deleted file mode 100644 index 8478a151..00000000 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs +++ /dev/null @@ -1,14 +0,0 @@ --- @inline Snapshot.RecursionInlined02.append always -module Snapshot.RecursionInlined02 where - -data List a = Nil | Cons a (List a) -infixr 5 Cons as : - -append :: forall a. List a -> List a -> List a -append Nil ys = ys -append (Cons x xs) ys = Cons x (append xs ys) - -infixr 4 append as <> - -test1 :: List String -> List String -test1 z = ("a" : "b" : "c" : "d" : "e" : "f" : "g" : z) <> Nil \ No newline at end of file From fa03c933ea999c3e5db5054727de6c950cd81fd2 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 12:12:48 +0300 Subject: [PATCH 09/14] Adds NeutLit to guard --- .../Snapshot.RecursionInlined02.js | 16 ++++++++++++++++ .../snapshots/Snapshot.RecursionInlined02.purs | 17 +++++++++++++++++ src/PureScript/Backend/Optimizer/Semantics.purs | 1 + 3 files changed, 34 insertions(+) create mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js create mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlined02.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js new file mode 100644 index 00000000..6a873805 --- /dev/null +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js @@ -0,0 +1,16 @@ +// @inline Snapshot.RecursionInlined02.addStuff always +const $List = (tag, _1, _2) => ({tag, _1, _2}); +const Nil = /* #__PURE__ */ $List("Nil"); +const Cons = value0 => value1 => $List("Cons", value0, value1); +const addStuff = x => ys => { + if (x === 0) { return ys; } + return 1 + addStuff(x - 1 | 0)(ys) | 0; +}; +const test1 = 7; +const test2 = z => 2 + addStuff(1)((() => { + if (z === 0) { return 5; } + const $0 = z - 1 | 0; + if ($0 === 0) { return 6; } + return 2 + addStuff($0 - 1 | 0)(5) | 0; +})()) | 0; +export {$List, Cons, Nil, addStuff, test1, test2}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs new file mode 100644 index 00000000..e9d90507 --- /dev/null +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs @@ -0,0 +1,17 @@ +-- @inline Snapshot.RecursionInlined02.addStuff always +module Snapshot.RecursionInlined02 where + +import Prelude + +data List a = Nil | Cons a (List a) +infixr 5 Cons as : + +addStuff :: Int -> Int -> Int +addStuff x ys = if x == zero then ys else one + addStuff (x - one) ys + +infixr 4 addStuff as ++ + +test1 :: Int +test1 = 3 ++ 4 +test2 :: Int -> Int +test2 z = 3 ++ z ++ 5 \ No newline at end of file diff --git a/src/PureScript/Backend/Optimizer/Semantics.purs b/src/PureScript/Backend/Optimizer/Semantics.purs index 133bc707..eb07f81a 100644 --- a/src/PureScript/Backend/Optimizer/Semantics.purs +++ b/src/PureScript/Backend/Optimizer/Semantics.purs @@ -193,6 +193,7 @@ recursable = go go arr | Just { head, tail } <- Array.uncons arr = case head of NeutData _ _ _ _ _ -> go tail + NeutLit _ -> go tail _ -> false | otherwise = true From 1f61d1c2889f6a041c8b8a34cd01f75f81833014 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 12:16:09 +0300 Subject: [PATCH 10/14] Smarter function --- .../Snapshot.RecursionInlined02.js | 22 ++++++++++++++----- .../Snapshot.RecursionInlined02.purs | 5 ++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js index 6a873805..64399cee 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js @@ -3,14 +3,24 @@ const $List = (tag, _1, _2) => ({tag, _1, _2}); const Nil = /* #__PURE__ */ $List("Nil"); const Cons = value0 => value1 => $List("Cons", value0, value1); const addStuff = x => ys => { - if (x === 0) { return ys; } - return 1 + addStuff(x - 1 | 0)(ys) | 0; + if (x > 0) { return 1 + addStuff(x - 1 | 0)(ys) | 0; } + if (x < 0) { return -1 + addStuff(x + 1 | 0)(ys) | 0; } + return ys; }; const test1 = 7; const test2 = z => 2 + addStuff(1)((() => { - if (z === 0) { return 5; } - const $0 = z - 1 | 0; - if ($0 === 0) { return 6; } - return 2 + addStuff($0 - 1 | 0)(5) | 0; + if (z > 0) { + const $0 = z - 1 | 0; + if ($0 > 0) { return 2 + addStuff($0 - 1 | 0)(5) | 0; } + if ($0 < 0) { return 0 + addStuff($0 + 1 | 0)(5) | 0; } + return 6; + } + if (z < 0) { + const $0 = z + 1 | 0; + if ($0 > 0) { return 0 + addStuff($0 - 1 | 0)(5) | 0; } + if ($0 < 0) { return -2 + addStuff($0 + 1 | 0)(5) | 0; } + return 4; + } + return 5; })()) | 0; export {$List, Cons, Nil, addStuff, test1, test2}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs index e9d90507..6703dc9e 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs @@ -7,7 +7,10 @@ data List a = Nil | Cons a (List a) infixr 5 Cons as : addStuff :: Int -> Int -> Int -addStuff x ys = if x == zero then ys else one + addStuff (x - one) ys +addStuff x ys + | x > 0 = 1 + addStuff (x - 1) ys + | x < 0 = (-1) + addStuff (x + 1) ys + | otherwise = ys infixr 4 addStuff as ++ From e3c89426dbb856b9bfe5b84178773f7e70485c5f Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 12:21:35 +0300 Subject: [PATCH 11/14] Adds test for non-terminating recursion --- .../Snapshot.RecursionInlinedBroken.js | 17 +++++++++++++++++ .../Snapshot.RecursionInlinedBroken.purs | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js create mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js new file mode 100644 index 00000000..c4d23a39 --- /dev/null +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js @@ -0,0 +1,17 @@ +// @inline Snapshot.RecursionInlinedBroken.addStuffBroken always +// This will recurse out of control and stop when it hits the recursion limit +const $List = (tag, _1, _2) => ({tag, _1, _2}); +const Nil = /* #__PURE__ */ $List("Nil"); +const Cons = value0 => value1 => $List("Cons", value0, value1); +const addStuffBroken = v => v1 => { + if (v === 0) { return v1; } + return 1 + addStuffBroken(v - 1 | 0)(v1) | 0; +}; +const test1 = /* #__PURE__ */ (() => 2 + addStuffBroken(-5)(4) | 0)(); +const test2 = z => 2 + addStuffBroken(-5)((() => { + if (z === 0) { return 5; } + const $0 = z - 1 | 0; + if ($0 === 0) { return 6; } + return 2 + addStuffBroken($0 - 1 | 0)(5) | 0; +})()) | 0; +export {$List, Cons, Nil, addStuffBroken, test1, test2}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs b/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs new file mode 100644 index 00000000..1cb71e46 --- /dev/null +++ b/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs @@ -0,0 +1,19 @@ +-- @inline Snapshot.RecursionInlinedBroken.addStuffBroken always +-- This will recurse out of control and stop when it hits the recursion limit +module Snapshot.RecursionInlinedBroken where + +import Prelude + +data List a = Nil | Cons a (List a) +infixr 5 Cons as : + +addStuffBroken :: Int -> Int -> Int +addStuffBroken 0 ys = ys +addStuffBroken x ys = 1 + addStuffBroken (x - 1) ys + +infixr 4 addStuffBroken as ++ + +test1 :: Int +test1 = (-3) ++ 4 +test2 :: Int -> Int +test2 z = (-3) ++ z ++ 5 \ No newline at end of file From d92b4f19e55ad8d0537dddd871a5258b008d23d2 Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 13:28:32 +0300 Subject: [PATCH 12/14] Adds broken test --- .../test/snapshots-out/Snapshot.RecursionInlined02.js | 2 +- .../test/snapshots-out/Snapshot.RecursionInlinedBroken.js | 2 +- backend-es/test/snapshots/Snapshot.RecursionInlined02.purs | 2 +- .../test/snapshots/Snapshot.RecursionInlinedBroken.purs | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js index 64399cee..9c8a958e 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined02.js @@ -7,7 +7,7 @@ const addStuff = x => ys => { if (x < 0) { return -1 + addStuff(x + 1 | 0)(ys) | 0; } return ys; }; -const test1 = 7; +const test1 = 42; const test2 = z => 2 + addStuff(1)((() => { if (z > 0) { const $0 = z - 1 | 0; diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js index c4d23a39..d3aabe35 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlinedBroken.js @@ -7,7 +7,7 @@ const addStuffBroken = v => v1 => { if (v === 0) { return v1; } return 1 + addStuffBroken(v - 1 | 0)(v1) | 0; }; -const test1 = /* #__PURE__ */ (() => 2 + addStuffBroken(-5)(4) | 0)(); +const test1 = v => 2 + addStuffBroken(-5)(4) | 0; const test2 = z => 2 + addStuffBroken(-5)((() => { if (z === 0) { return 5; } const $0 = z - 1 | 0; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs index 6703dc9e..cb3425b0 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined02.purs @@ -15,6 +15,6 @@ addStuff x ys infixr 4 addStuff as ++ test1 :: Int -test1 = 3 ++ 4 +test1 = 38 ++ 4 test2 :: Int -> Int test2 z = 3 ++ z ++ 5 \ No newline at end of file diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs b/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs index 1cb71e46..9628c151 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlinedBroken.purs @@ -13,7 +13,10 @@ addStuffBroken x ys = 1 + addStuffBroken (x - 1) ys infixr 4 addStuffBroken as ++ -test1 :: Int -test1 = (-3) ++ 4 +-- we make this Unit -> Int so that the module can be loaded with an +-- import statement. otherwise, it will execute the broken (-3) ++ 4 and the +-- tests will fail +test1 :: Unit -> Int +test1 _ = (-3) ++ 4 test2 :: Int -> Int test2 z = (-3) ++ z ++ 5 \ No newline at end of file From 0282279c0158e9dcd39116f9bb69ba87ef2c942f Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 13:39:47 +0300 Subject: [PATCH 13/14] Adds composition test --- .../snapshots-out/Snapshot.RecursionInlined03.js | 6 ++++++ .../test/snapshots/Snapshot.RecursionInlined03.purs | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js create mode 100644 backend-es/test/snapshots/Snapshot.RecursionInlined03.purs diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js new file mode 100644 index 00000000..2fe576f0 --- /dev/null +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js @@ -0,0 +1,6 @@ +// @inline Snapshot.RecursionInlined03.compose always +const id = a => a; +const compose = ab => bc => a => bc(ab(a)); +const test1 = a => a; +const test2 = z => a => z(a); +export {compose, id, test1, test2}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs new file mode 100644 index 00000000..ad98ead8 --- /dev/null +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs @@ -0,0 +1,13 @@ +-- @inline Snapshot.RecursionInlined03.compose always +module Snapshot.RecursionInlined03 where + +compose :: forall a b c. (a -> b) -> (b -> c) -> (a -> c) +compose ab bc a = bc (ab a) + +id :: forall a. a -> a +id a = a + +test1 :: Int -> Int +test1 =compose id (compose id (compose id (compose id id))) +test2 :: (Int -> Int) -> Int -> Int +test2 z = compose id (compose id (compose z (compose id id))) \ No newline at end of file From a93cd811f6651e4be1a8c19f709df125e21bde4f Mon Sep 17 00:00:00 2001 From: Mike Solomon Date: Mon, 26 Jun 2023 15:12:43 +0300 Subject: [PATCH 14/14] Adds test that does not work yet for variants --- .../Snapshot.RecursionInlined03.js | 54 ++++++++++++++++--- .../Snapshot.RecursionInlined03.purs | 36 +++++++++---- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js b/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js index 2fe576f0..97adba14 100644 --- a/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js +++ b/backend-es/test/snapshots-out/Snapshot.RecursionInlined03.js @@ -1,6 +1,48 @@ -// @inline Snapshot.RecursionInlined03.compose always -const id = a => a; -const compose = ab => bc => a => bc(ab(a)); -const test1 = a => a; -const test2 = z => a => z(a); -export {compose, id, test1, test2}; +// @inline Snapshot.RecursionInlined03.append always +// This doesn't quite work yet, because the inlining of append depends +// on the analysis of the local `b`, which we don't have (easy) access to. +// If we can somehow grab its usage, we'd likely see that access + case == total +import * as Partial from "../Partial/index.js"; +const List = x => x; +const nil = {type: "nil", value: undefined}; +const cons = head => tail => ({type: "cons", value: {head, tail}}); +const append = v => b => { + if (v.type === "cons") { return {type: "cons", value: {head: v.value.head, tail: append(v.value.tail)(b)}}; } + if (v.type === "nil") { return b; } + return Partial._crashWith("Data.Variant: pattern match failure [" + v.type + "]"); +}; +const test1 = { + type: "cons", + value: { + head: "a", + tail: { + type: "cons", + value: { + head: "b", + tail: /* #__PURE__ */ append({type: "cons", value: {head: "c", tail: nil}})({ + type: "cons", + value: {head: "d", tail: {type: "cons", value: {head: "e", tail: {type: "cons", value: {head: "f", tail: {type: "cons", value: {head: "g", tail: nil}}}}}}} + }) + } + } + } +}; +const test2 = z => ( + { + type: "cons", + value: { + head: "a", + tail: { + type: "cons", + value: { + head: "b", + tail: append({type: "cons", value: {head: "c", tail: z}})({ + type: "cons", + value: {head: "d", tail: {type: "cons", value: {head: "e", tail: {type: "cons", value: {head: "f", tail: {type: "cons", value: {head: "g", tail: nil}}}}}}} + }) + } + } + } + } +); +export {List, append, cons, nil, test1, test2}; diff --git a/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs b/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs index ad98ead8..aefd068a 100644 --- a/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs +++ b/backend-es/test/snapshots/Snapshot.RecursionInlined03.purs @@ -1,13 +1,31 @@ --- @inline Snapshot.RecursionInlined03.compose always +-- @inline Snapshot.RecursionInlined03.append always +-- This doesn't quite work yet, because the inlining of append depends +-- on the analysis of the local `b`, which we don't have (easy) access to. +-- If we can somehow grab its usage, we'd likely see that access + case == total module Snapshot.RecursionInlined03 where -compose :: forall a b c. (a -> b) -> (b -> c) -> (a -> c) -compose ab bc a = bc (ab a) +import Prelude -id :: forall a. a -> a -id a = a +import Data.Variant (Variant, inj, case_, on) +import Type.Proxy (Proxy(..)) -test1 :: Int -> Int -test1 =compose id (compose id (compose id (compose id id))) -test2 :: (Int -> Int) -> Int -> Int -test2 z = compose id (compose id (compose z (compose id id))) \ No newline at end of file +newtype List a = List (Variant (nil :: Unit, cons :: { head :: a, tail :: List a })) + +cons :: forall a. a -> List a -> List a +cons head tail = List $ inj (Proxy :: _ "cons") {head, tail} +nil :: forall a. List a +nil = List $ inj (Proxy :: _ "nil") unit +infixr 5 cons as : + +append :: forall a. List a -> List a -> List a +append (List a) b = (case_ + # on (Proxy :: _ "nil") (\_ -> b) + # on (Proxy :: _ "cons") (\{head, tail} -> cons head (append tail b))) a + +infixr 4 append as <> + +test1 :: List String +test1 = ("a" : "b" : "c" : nil) <> ("d" : "e" : "f" : "g" : nil) + +test2 :: List String -> List String +test2 z = ("a" : "b" : "c" : z) <> ("d" : "e" : "f" : "g" : nil) \ No newline at end of file