diff --git a/bench/goldens/fnew_Bench.TupleFold.txt b/bench/goldens/fnew_Bench.TupleFold.txt new file mode 100644 index 00000000..44ec1a19 --- /dev/null +++ b/bench/goldens/fnew_Bench.TupleFold.txt @@ -0,0 +1,8 @@ +chunk: Bench.TupleFold.lua +runtime: LuaJIT 2.1.1741730670 +main-chunk FNEW: 2 +function-body FNEW: 1 +total FNEW: 3 +prototypes: 4 +function-body FNEW sites: + Bench.TupleFold.lua:15 diff --git a/bench/goldens/trace_tuple_fold.txt b/bench/goldens/trace_tuple_fold.txt new file mode 100644 index 00000000..8e815a02 --- /dev/null +++ b/bench/goldens/trace_tuple_fold.txt @@ -0,0 +1,13 @@ +spec: tuple_fold +runtime: LuaJIT 2.1.1741730670 +workload: n=5000000 reps=4 result=124999750000 +aborts (distinct site -- reason): + Bench.TupleFold.lua:15 -- NYI: bytecode FNEW + Bench.TupleFold.lua:16 -- NYI: bytecode UCLO + Bench.TupleFold.lua:9 -- inner loop in root trace + tuple_fold.lua:22 -- inner loop in root trace +bytecode end state (J*=compiled, I*=blacklisted): + Bench.TupleFold.lua:1 JFUNCF + Bench.TupleFold.lua:8 JLOOP + tuple_fold.lua:21 JLOOP +counts: aborts=4 compiled=3 blacklisted=0 diff --git a/bench/macro/tuple_fold.lua b/bench/macro/tuple_fold.lua new file mode 100644 index 00000000..dcb510d6 --- /dev/null +++ b/bench/macro/tuple_fold.lua @@ -0,0 +1,26 @@ +-- A fold-shaped loop carrying a Tuple accumulator. Call-pattern +-- specialization (issue #208) passes the two fields as raw loop +-- parameters, so the compiled loop does the same two-variable +-- accumulation as the ideal below and allocates the box only on the +-- exit path -- one table per call instead of one per iteration. +return { + artifact = "Bench.TupleFold", + n = 5e6, + -- Ten calls of n/10 rather than one call of n: the same total work + -- for the timing runners, but keeps each hot-counter's bumps within + -- one recording attempt. See the note in array_foldl.lua. + drive = function(mod, n) + local acc + for _ = 1, 10 do + acc = mod.run(n / 10) + end + return acc + end, + ideal = function(n) + local s, i = 0, 0 + while i < n do + s, i = s + i, i + 1 + end + return s + end, +} diff --git a/changelog.d/20260715_120000_unisay_spec_constr.md b/changelog.d/20260715_120000_unisay_spec_constr.md new file mode 100644 index 00000000..5041829e --- /dev/null +++ b/changelog.d/20260715_120000_unisay_spec_constr.md @@ -0,0 +1,15 @@ +### Added + +- Call-pattern specialization for recursive bindings, GHC's SpecConstr + discipline on the IR (#208): a recursive function that scrutinizes a + parameter and passes a known constructor at that position in its recursive + calls gets a specialized copy taking the constructor's fields as separate + parameters, and every qualifying call site is rewritten to it. Fold-shaped + loops with `Tuple`/`Maybe`/`Either` accumulators then carry raw values + instead of allocating a box per iteration; the box materializes only where + it escapes (the exit path). Specializations are capped per binding and + minted one layer per specialize+dce round, so nested accumulators unbox + incrementally without unbounded code growth. On the new `tuple_fold` + macro benchmark the compiled loop drops the per-iteration table build, + running ~2.5x faster under PUC Lua 5.1 (LuaJIT already sank the + allocation inside its trace, so it is unchanged there). diff --git a/lib/Language/PureScript/Backend/IR/Optimizer.hs b/lib/Language/PureScript/Backend/IR/Optimizer.hs index 26ea0978..c2b46e36 100644 --- a/lib/Language/PureScript/Backend/IR/Optimizer.hs +++ b/lib/Language/PureScript/Backend/IR/Optimizer.hs @@ -36,6 +36,12 @@ import Language.PureScript.Backend.IR.Pass , runSteps , runStepsChecked ) +import Language.PureScript.Backend.IR.Query + ( CtorShape (..) + , ctorShapeTag + , resolveKnownCtorApp + ) +import Language.PureScript.Backend.IR.SpecConstr (specConstr) import Language.PureScript.Backend.IR.Supply (SupplyM, freshName, runSupply) import Language.PureScript.Backend.IR.Types ( AlgebraicType (SumType) @@ -150,7 +156,16 @@ optimizerPipeline policy = -- the case-of-known-constructor folds (#177/#213/#214), collapsing -- Maybe/Either/Writer/State chains into straight-line code. Code growth -- is bounded by 'inlineSizeBudget'. - RunFixpoint "specialize+dce" (specializePass :| [dcePass]) + -- + -- Call-pattern specialization (issue #208) rides in the same fixpoint: + -- a recursive binding whose recursion passes a known constructor at a + -- scrutinized parameter position gets an unboxed specialized copy, and + -- the next optimize round's constructor folds collapse the reboxes its + -- body carries. Each round mints one specialization layer, so the + -- fixpoint provides the bounded iteration a nested accumulator needs; + -- the per-binding cap in Language.PureScript.Backend.IR.SpecConstr + -- keeps the minting finite. + RunFixpoint "specialize+dce" (specializePass :| [dcePass, specConstrPass]) , -- Rebuild sharing for the foreign-accessor reads that dissolution -- and the call-site pastes above duplicated: a read surviving at -- two or more sites is re-bound to its linker name, which stage-2 @@ -223,6 +238,13 @@ optimizerPipeline policy = , passRequires = guc , passEnsures = guc } + specConstrPass = + Pass + { passName = "spec-constr" + , passRun = specConstr (uncurryVeto policy) + , passRequires = guc + , passEnsures = guc + } dcePass = Pass { passName = "dce" @@ -1183,7 +1205,10 @@ propagateKnownCtorThroughLet env = \case go _before [] = Nothing go before (grouping : after) = case grouping of Standalone (_bAnn, name, rhs) - | Just (algTy, arity, args, tag) ← resolveKnownCtorApp env rhs + | Just (shape, args) ← resolveKnownCtorApp env rhs + , let algTy = ctorShapeType shape + arity = fromIntegral (length args) ∷ Natural + tag = ctorShapeTag shape , -- A self-referencing RHS cannot arise under GUC (a Standalone RHS -- does not see its own binder), but 'optimizedExpression' also runs -- on non-GUC input; dropping the binding would then dangle the @@ -1265,111 +1290,17 @@ reduceKnownCtorRefRead env = -- Field reads fold only at the constructor's own algebraic type, as -- in 'reduceKnownConstructor'. DataArgumentByIndex ann algTy i spine - | Just (ctorAlgTy, _arity, args, _tag) ← resolveKnownCtorApp env spine - , algTy == ctorAlgTy + | Just (shape, args) ← resolveKnownCtorApp env spine + , algTy == ctorShapeType shape , Just arg ← args !!? fromIntegral i → Just (setAnn ann arg) -- Tag reads fold for sum types only, as in 'reduceKnownConstructor'. ReflectCtor ann spine - | Just (SumType, _arity, _args, tag) ← resolveKnownCtorApp env spine → - Just (LiteralString ann tag) + | Just (shape, _args) ← resolveKnownCtorApp env spine + , SumType ← ctorShapeType shape → + Just (LiteralString ann (ctorShapeTag shape)) _ → Nothing -{- | Recognise a known saturated constructor value and return its algebraic -type, arity, field arguments, and tag string. It covers every shape a -constructor value takes after uncurrying (see Note [Constructor applications -are saturated]): - - * an in-place 'Ctor' node (saturated by construction); - * an n-ary worker call @AppN (Ref Cʷ) [a₁,…,aₙ]@ — the shape the early - uncurry run rewrites a saturated arity-≥2 site into. 'unwindApp' does - not flatten a multi-argument 'AppN' (Note [n-ary application]), so this - shape is matched directly, not through the unary spine — miss it and - the folds silently stop firing on monadic chains; - * a curried unary spine @App (… (App (Ref C) a₁) …) aₙ@ — an arity-1 - constructor reference, or an arity-≥2 curried wrapper reference a site - that saturates only after magicDo/flattening still carries before the - late uncurry run. - -A reference is resolved through the inline environment by 'ctorFunctionShape', -which reads only the constructor's declared arity and tag — it pastes no -'Ctor' node, so a chain that does not fold is not pessimised into pasted -constructor thunks (issue #180). The result is returned only when the -application is saturated (argument count equal to the declared arity). --} -resolveKnownCtorApp - ∷ InlineEnv → Exp → Maybe (AlgebraicType, Natural, [Exp], Text) -resolveKnownCtorApp env = \case - Ctor _ algTy modName tyName ctorName args → - Just - ( algTy - , fromIntegral (length args) - , args - , ctorId modName tyName ctorName - ) - AppN _ (Ref _ ctorRef) args - | Just (algTy, arity, tag) ← ctorFunctionShape env ctorRef - , arity == length args → - Just (algTy, fromIntegral arity, toList args, tag) - expr - | (Ref _ ctorRef, args@(_ : _)) ← unwindApp expr - , Just (algTy, arity, tag) ← ctorFunctionShape env ctorRef - , arity == length args → - Just (algTy, fromIntegral arity, args, tag) - _ → Nothing - -{- | The algebraic type, arity, and tag of a top-level binding that is a -constructor /function/: the manifest lambda chain 'mkConstructor' emits over -a saturated 'Ctor' of references to its parameters, the n-ary worker the -uncurry split derives from it, or the curried wrapper delegating to such a -worker. Resolving reads only the declaration, never pasting a 'Ctor' node. -The visited set makes the wrapper→worker hop terminate on the arbitrary -(possibly cyclic) input 'optimizedExpression' also runs on. --} -ctorFunctionShape - ∷ InlineEnv → Qualified Name → Maybe (AlgebraicType, Int, Text) -ctorFunctionShape env = go Set.empty - where - go visited ctorRef - | ctorRef `Set.member` visited = Nothing - | otherwise = do - rhs ← Map.lookup ctorRef env - let (params, body) = peelCtorParams rhs - case body of - Ctor _ algTy modName tyName ctorName args - | argsAreRefsTo params args → - Just (algTy, length params, ctorId modName tyName ctorName) - AppN _ (Ref _ workerRef) wargs - | argsAreRefsTo params (toList wargs) - , Just (algTy, arity, tag) ← go (Set.insert ctorRef visited) workerRef - , arity == length params → - Just (algTy, arity, tag) - _ → Nothing - -{- | Peel leading lambda parameters (through unary 'Abs' and n-ary 'AbsN'), as -long as every one is named, returning the names in order and the body. --} -peelCtorParams ∷ Exp → ([Name], Exp) -peelCtorParams = go [] - where - go ∷ [Name] → Exp → ([Name], Exp) - go acc = \case - AbsN _ params body - | Just names ← traverse paramName (toList params) → - go (acc <> names) body - e → (acc, e) - -{- | Whether the expressions are exactly local references to the given names, -in order — a constructor lambda passing its parameters straight through. --} -argsAreRefsTo ∷ [Name] → [Exp] → Bool -argsAreRefsTo names args = - length names == length args && and (zipWith isRefTo names args) - where - isRefTo ∷ Name → Exp → Bool - isRefTo name (Ref _ (Local n)) = n == name - isRefTo _ _ = False - {- | Resolve a method projection off a known top-level dictionary (issue #180). When @dict@ is a reference to a top-level 'LiteralObject' binding, @dict.method@ is replaced by the method expression itself — the concrete diff --git a/lib/Language/PureScript/Backend/IR/Query.hs b/lib/Language/PureScript/Backend/IR/Query.hs index 25df40ac..2141aab7 100644 --- a/lib/Language/PureScript/Backend/IR/Query.hs +++ b/lib/Language/PureScript/Backend/IR/Query.hs @@ -6,17 +6,25 @@ import Data.Map qualified as Map import Data.Set qualified as Set import Language.PureScript.Backend.IR.Linker (UberModule (..)) import Language.PureScript.Backend.IR.Names - ( Name (Name) + ( CtorName + , ModuleName + , Name (Name) , Qualified (Imported, Local) + , TyName , runModuleName ) import Language.PureScript.Backend.IR.Types - ( Exp + ( AlgebraicType + , Exp + , RawExp (..) , bindingNames , countFreeRef , countFreeRefs + , ctorId , listGrouping + , paramName , subexpressions + , unwindApp ) import Language.PureScript.Backend.IR.Types qualified as IR import Language.PureScript.Names (runtimeLazyName) @@ -48,6 +56,119 @@ findPrimModuleInExpr expr = Local _name → False Imported moduleName _name → runModuleName moduleName == "Prim" +-------------------------------------------------------------------------------- +-- Known-constructor resolution ------------------------------------------------ + +{- | The identity of a data constructor: its algebraic type and the +qualified name triple 'ctorId' renders into the runtime tag string. +-} +data CtorShape = CtorShape + { ctorShapeType ∷ AlgebraicType + , ctorShapeModule ∷ ModuleName + , ctorShapeTyName ∷ TyName + , ctorShapeCtor ∷ CtorName + } + deriving stock (Eq, Ord, Show) + +-- | The runtime tag string of the constructor ('ctorId'). +ctorShapeTag ∷ CtorShape → Text +ctorShapeTag CtorShape {ctorShapeModule, ctorShapeTyName, ctorShapeCtor} = + ctorId ctorShapeModule ctorShapeTyName ctorShapeCtor + +{- | Recognise a known saturated constructor value and return its shape +and field arguments. It covers every shape a constructor value takes +after uncurrying (see Note [Constructor applications are saturated] in +"Language.PureScript.Backend.IR.Types"): + + * an in-place 'Ctor' node (saturated by construction); + * an n-ary worker call @AppN (Ref Cʷ) [a₁,…,aₙ]@ — the shape the early + uncurry run rewrites a saturated arity-≥2 site into. 'unwindApp' does + not flatten a multi-argument 'AppN' (Note [n-ary application]), so this + shape is matched directly, not through the unary spine — miss it and + the folds silently stop firing on monadic chains; + * a curried unary spine @App (… (App (Ref C) a₁) …) aₙ@ — an arity-1 + constructor reference, or an arity-≥2 curried wrapper reference a site + that saturates only after magicDo/flattening still carries before the + late uncurry run. + +A reference is resolved through the given environment by +'ctorFunctionShape', which reads only the constructor's declared shape — +it pastes no 'Ctor' node, so a chain that does not fold is not pessimised +into pasted constructor thunks (issue #180). The result is returned only +when the application is saturated (argument count equal to the declared +arity). +-} +resolveKnownCtorApp + ∷ Map (Qualified Name) Exp → Exp → Maybe (CtorShape, [Exp]) +resolveKnownCtorApp env = \case + Ctor _ algTy modName tyName ctorName args → + Just (CtorShape algTy modName tyName ctorName, args) + AppN _ (Ref _ ctorRef) args + | Just (shape, arity) ← ctorFunctionShape env ctorRef + , arity == length args → + Just (shape, toList args) + expr + | (Ref _ ctorRef, args@(_ : _)) ← unwindApp expr + , Just (shape, arity) ← ctorFunctionShape env ctorRef + , arity == length args → + Just (shape, args) + _ → Nothing + +{- | The shape and arity of a binding that is a constructor /function/: +the manifest lambda chain 'Language.PureScript.Backend.IR.mkConstructor' +emits over a saturated 'Ctor' of references to its parameters, the n-ary +worker the uncurry split derives from it, or the curried wrapper +delegating to such a worker. Resolving reads only the declaration, never +pasting a 'Ctor' node. The visited set makes the wrapper→worker hop +terminate on arbitrary (possibly cyclic) input. +-} +ctorFunctionShape + ∷ Map (Qualified Name) Exp → Qualified Name → Maybe (CtorShape, Int) +ctorFunctionShape env = go Set.empty + where + go visited ctorRef + | ctorRef `Set.member` visited = Nothing + | otherwise = do + rhs ← Map.lookup ctorRef env + let (params, body) = peelCtorParams rhs + case body of + Ctor _ algTy modName tyName ctorName args + | argsAreRefsTo params args → + Just + ( CtorShape algTy modName tyName ctorName + , length params + ) + AppN _ (Ref _ workerRef) wargs + | argsAreRefsTo params (toList wargs) + , Just (shape, arity) ← go (Set.insert ctorRef visited) workerRef + , arity == length params → + Just (shape, arity) + _ → Nothing + +{- | Peel leading lambda parameters (through unary 'Abs' and n-ary 'AbsN'), as +long as every one is named, returning the names in order and the body. +-} +peelCtorParams ∷ Exp → ([Name], Exp) +peelCtorParams = go [] + where + go ∷ [Name] → Exp → ([Name], Exp) + go acc = \case + AbsN _ params body + | Just names ← traverse paramName (toList params) → + go (acc <> names) body + e → (acc, e) + +{- | Whether the expressions are exactly local references to the given names, +in order — a constructor lambda passing its parameters straight through. +-} +argsAreRefsTo ∷ [Name] → [Exp] → Bool +argsAreRefsTo names args = + length names == length args && and (zipWith isRefTo names args) + where + isRefTo ∷ Name → Exp → Bool + isRefTo name (Ref _ (Local n)) = n == name + isRefTo _ _ = False + collectBoundNames ∷ Exp → Set Name collectBoundNames = (`execAccum` Set.empty) . transformMOf subexpressions \e → diff --git a/lib/Language/PureScript/Backend/IR/SpecConstr.hs b/lib/Language/PureScript/Backend/IR/SpecConstr.hs new file mode 100644 index 00000000..c213d7bd --- /dev/null +++ b/lib/Language/PureScript/Backend/IR/SpecConstr.hs @@ -0,0 +1,519 @@ +{- | Call-pattern specialization for recursive bindings (issue #208). + +A recursive function that carries a constructor accumulator allocates +the box on every iteration only to take it apart at the top of the next +one: + +> go acc = case acc of +> Tuple s i +> | i >= n → acc +> | otherwise → go (Tuple (s + i) (i + 1)) + +This is GHC's SpecConstr discipline transplanted to the IR: for a +recursive-group member whose body scrutinizes a parameter and whose +call sites within the group pass a known constructor at that position, +mint a specialized copy taking the constructor's fields as separate +parameters, and rewrite the qualifying call sites — the group's own and +everyone else's — to call it directly. The original binding stays +behind as the boxed entry point for callers that do not know the +constructor. + +== The specialization + +For a member @f = AbsN [p₁ … pₙ] body@ and a call pattern ⟨k, K\/m⟩ +(sites pass constructor @K@ of @m@ fields at position @k@): + + * the specialized copy is + @f$sc\\ = AbsN [p₁ … pₖ₋₁, f₁ … fₘ, pₖ₊₁ … pₙ] body′@, + where @body′@ is a binder-freshened copy of @body@ with every read + of @pₖ@ replaced by the rebox @K f₁ … fₘ@; + * a qualifying site @f(a₁, …, K b₁ … bₘ, …, aₙ)@ becomes + @f$sc…(a₁, …, b₁ … bₘ, …, aₙ)@. + +No folding happens here: the reboxes pasted at constructor-eliminating +reads ('ReflectCtor', 'DataArgumentByIndex') meet the +case-of-known-constructor folds (issue #177) in the surrounding +fixpoint's optimize pass, which collapse them to the tag string and the +field references — the specialized loop then carries raw values. A +rebox at a whole-value read (an exit path returning the accumulator) +survives as a real allocation, but it runs only where the box genuinely +escapes, not per iteration. + +== Guards + + * Recursive bindings only: the non-recursive case is ordinary + inlining plus the constructor folds. + * The scrutiny requirement: position @k@ qualifies only when the + body eliminates the parameter through a tag or field read — + specializing an unscrutinized box would only move the allocation. + * The pattern requirement: the constructor must appear at a call + site within the group itself, so the recursion is what carries the + box. Once minted, every qualifying site in the module is rewritten + — an entry call @go (Tuple 0 0)@ included, which is what lets DCE + drop the boxed entry when nobody boxed is left calling it. + * 'specConstrLimit' caps the specializations minted per binding (the + @-fspec-constr-count@ analogue), counted over the group's existing + @$sc@ siblings, so iterated runs cannot mint without bound. + * One run mints one layer instead of iterating to a fixpoint: a + pattern exposed by a specialized body (a nested accumulator) is + only visible after the surrounding fixpoint's optimize pass folds + the previous layer's reboxes, so the enclosing + specialize+dce fixpoint provides the bounded iteration and this + pass reports 'Unmodified' as soon as no unhandled pattern remains. + +== Names + +Minted names are deterministic (the supply is drawn only inside +'freshenBinders'): the specialized binding is +@\$sc\\@ — pattern-keyed, so a rerun that meets +the same pattern again finds the existing binding by name and rewrites +the new sites to it instead of minting a duplicate — and its field +parameters are @\$f\@. @$@ cannot occur in a source +identifier and no other pass mints an @$sc@ group, so the schemes +cannot collide. +-} +module Language.PureScript.Backend.IR.SpecConstr + ( specConstr + , specConstrLimit + ) where + +import Control.Lens (cosmosOf, toListOf, transformMOf, transformOf) +import Control.Monad.Writer.CPS + ( Writer + , WriterT + , runWriter + , runWriterT + , tell + , writer + ) +import Data.List qualified as List +import Data.List.NonEmpty qualified as NE +import Data.Map.Strict qualified as Map +import Data.Set qualified as Set +import Data.Text qualified as Text +import Language.PureScript.Backend.IR.Inliner qualified as Inliner +import Language.PureScript.Backend.IR.Linker (UberModule (..)) +import Language.PureScript.Backend.IR.Names + ( Name (..) + , QName (..) + , Qualified (..) + , nameToText + , renderCtorName + ) +import Language.PureScript.Backend.IR.Query + ( CtorShape (..) + , resolveKnownCtorApp + ) +import Language.PureScript.Backend.IR.Supply (SupplyM) +import Language.PureScript.Backend.IR.Types + ( Ann + , Exp + , Grouping (..) + , Parameter (..) + , RawExp (..) + , WasRewritten + , freshenBinders + , getAnn + , noAnn + , refLocal + , rewrittenIf + , setAnn + , subexpressions + ) + +{- | The @-fspec-constr-count@ analogue: how many specializations one +binding may accumulate across all runs, counted over its existing +@$sc@ siblings. +-} +specConstrLimit ∷ Int +specConstrLimit = 3 + +{- | Specialize the recursive bindings of the module on the +known-constructor call patterns their groups carry, and rewrite every +qualifying call site to the specialized copies. The 'Set' argument is +the veto collected by the optimizer ('inline' pragmas declare intent +about the name's call shape as-is); vetoed bindings are left alone. +-} +specConstr ∷ Set QName → UberModule → SupplyM (UberModule, WasRewritten) +specConstr vetoed uber@UberModule {..} = do + (bindings1, topSpecs) ← + second (List.sortOn specPos . concat) . List.unzip + <$> traverse processTopGrouping uberModuleBindings + let topActives = activesOf topSpecs + ((bindings2, exports2), changes) ← runWriterT do + tell (Any (any (isJust . specRHS) topSpecs)) + bindings' ← + traverse (traverse (traverse (processSite env topActives))) bindings1 + exports' ← + traverse (traverse (processSite env topActives)) uberModuleExports + pure (bindings', exports') + let changed = getAny changes + pure + ( if changed + then + uber + { uberModuleBindings = bindings2 + , uberModuleExports = exports2 + } + else uber + , rewrittenIf changed + ) + where + -- Constructor shapes at call sites resolve through the top-level + -- environment, exactly as the optimizer's known-constructor folds do. + env ∷ Map (Qualified Name) Exp + env = + Map.fromList + [ (Imported modname name, expr) + | Standalone (QName modname name, expr) ← uberModuleBindings + ] + + topVeto ∷ Qualified Name → Exp → Bool + topVeto ref rhs = case ref of + Imported modname name → QName modname name `Set.member` vetoed + Local _ → rootAnnVeto rhs + + -- Specialize one top-level recursive group: mint its specializations + -- and splice each in front of the member it specializes. Call sites + -- (its members' included) are rewritten by the later 'processSite' + -- sweep, which visits every top-level right-hand side. + processTopGrouping + ∷ Grouping (QName, Exp) → SupplyM (Grouping (QName, Exp), [Spec]) + processTopGrouping = \case + g@(Standalone _) → pure (g, []) + RecursiveGroup members → do + specs ← + groupSpecs env topVeto $ + [ (Imported modname name, name, rhs) + | (QName modname name, rhs) ← toList members + ] + let spliceMember (qname@(QName modname name), rhs) = + [ (QName modname (specName s), specRhs) + | s ← specs + , specOriginRef s == Imported modname name + , Just specRhs ← [specRHS s] + ] + <> [(qname, rhs)] + pure + ( RecursiveGroup (NE.fromList (spliceMember =<< toList members)) + , specs + ) + +-------------------------------------------------------------------------------- +-- Specializations of one recursive group -------------------------------------- + +{- | One call pattern's specialization: how call sites recognise it and, +when freshly minted this run, the binding to splice in. +-} +data Spec = Spec + { specOriginRef ∷ Qualified Name + -- ^ The group member being specialized, as call sites reference it + , specName ∷ Name + , specRef ∷ Qualified Name + -- ^ How rewritten call sites reference the specialization + , specArity ∷ Int + -- ^ The origin's parameter count (matched sites are saturated) + , specPos ∷ Int + -- ^ 0-based position of the specialized parameter + , specShape ∷ CtorShape + , specFields ∷ Int + -- ^ The constructor's field count + , specRHS ∷ Maybe Exp + {- ^ 'Just' for a fresh mint; 'Nothing' when an earlier run minted the + binding already (new sites are still rewritten to it) + -} + } + +{- | The specializations of one recursive group: for every call pattern +found in the group members' right-hand sides — a saturated call of a +scrutinizing member passing a known constructor at a scrutinized +position — either mint the specialized copy or, when a binding with the +pattern's name already exists in the group, re-register it for +site rewriting only. +-} +groupSpecs + ∷ Map (Qualified Name) Exp + -- ^ Environment resolving constructor references + → (Qualified Name → Exp → Bool) + -- ^ Veto: members this pass may not specialize + → [(Qualified Name, Name, Exp)] + -- ^ The group's members: site reference, name, right-hand side + → SupplyM [Spec] +groupSpecs env veto members = + snd <$> foldlM mintPattern (Map.empty, []) patterns + where + memberNames ∷ Set Name + memberNames = Set.fromList [name | (_ref, name, _rhs) ← members] + + -- Members eligible for specialization: a manifest lambda, not + -- vetoed, with at least one scrutinized named parameter. + candidates + ∷ Map (Qualified Name) (Name, NonEmpty (Parameter Ann), Exp, Set Int) + candidates = + Map.fromList + [ (ref, (name, params, body, scrutinized)) + | (ref, name, rhs@(AbsN _ params body)) ← members + , not (veto ref rhs) + , let eliminated = eliminatedNames body + scrutinized = + Set.fromList + [ k + | (k, ParamNamed _ p) ← zip [0 ..] (toList params) + , p `Set.member` eliminated + ] + , not (Set.null scrutinized) + ] + + -- The call patterns the recursion carries: for each saturated + -- group-internal call site of a candidate, the first scrutinized + -- position holding a known constructor. Positions the specialization + -- would leave parameterless are skipped ('AbsN' cannot bind zero + -- parameters). + patterns ∷ [(Qualified Name, Int, CtorShape, Int)] + patterns = + ordNub + [ (ref, k, shape, length fields) + | (_ref, _name, rhs) ← members + , AppN _ (Ref _ ref) args ← toListOf (cosmosOf subexpressions) rhs + , Just (_f, params, _body, scrutinized) ← [Map.lookup ref candidates] + , length args == length params + , (k, shape, fields) : _ ← + [ [ (k, shape, fields) + | k ← Set.toAscList scrutinized + , Just arg ← [toList args !!? k] + , Just (shape, fields) ← [resolveKnownCtorApp env arg] + , length params - 1 + length fields >= 1 + ] + ] + ] + + -- How many specializations a member already accumulated in earlier + -- runs: its @$sc@-prefixed siblings. + existingSpecCount ∷ Name → Int + existingSpecCount f = + length + [ () + | n ← Set.toList memberNames + , (nameToText f <> "$sc") `Text.isPrefixOf` nameToText n + ] + + mintPattern + ∷ (Map Name Int, [Spec]) + → (Qualified Name, Int, CtorShape, Int) + → SupplyM (Map Name Int, [Spec]) + mintPattern acc@(counts, specs) (ref, k, shape, fieldCount) = + case Map.lookup ref candidates of + Nothing → pure acc + Just (f, params, body, _scrutinized) → do + let sname = mkSpecName f k shape + spec = + Spec + { specOriginRef = ref + , specName = sname + , specRef = case ref of + Imported modname _ → Imported modname sname + Local _ → Local sname + , specArity = length params + , specPos = k + , specShape = shape + , specFields = fieldCount + , specRHS = Nothing + } + if sname `Set.member` memberNames + then pure (counts, spec : specs) + else do + let used = Map.findWithDefault (existingSpecCount f) f counts + if used >= specConstrLimit + then pure acc + else + mintRHS params body k shape fieldCount sname <&> \case + Nothing → acc + Just rhs → + ( Map.insert f (used + 1) counts + , spec {specRHS = Just rhs} : specs + ) + +{- | Build the specialized right-hand side: a binder-freshened copy of +the member with the parameter at the pattern position replaced by the +constructor's fields, and every read of it in the body replaced by the +rebox — which the case-of-known-constructor folds then collapse at +every eliminating read. +-} +mintRHS + ∷ NonEmpty (Parameter Ann) + → Exp + → Int + → CtorShape + → Int + → Name + → SupplyM (Maybe Exp) +mintRHS params body k shape fieldCount sname = do + fresh ← freshenBinders (AbsN noAnn params body) + pure case fresh of + AbsN _ params' body' + | (pre, ParamNamed _ pk : post) ← List.splitAt k (toList params') + , Just newParams ← nonEmpty (pre <> fieldParams <> post) → + let rebox = + Ctor + noAnn + (ctorShapeType shape) + (ctorShapeModule shape) + (ctorShapeTyName shape) + (ctorShapeCtor shape) + (refLocal <$> fieldNames) + subst = \case + r@(Ref _ (Local n)) | n == pk → setAnn (getAnn r) rebox + e → e + in Just (AbsN noAnn newParams (transformOf subexpressions subst body')) + _ → Nothing + where + fieldNames ∷ [Name] + fieldNames = + [ Name (nameToText sname <> "$f" <> Text.pack (show i)) + | i ← [1 .. fieldCount] + ] + fieldParams = ParamNamed noAnn <$> fieldNames + +mkSpecName ∷ Name → Int → CtorShape → Name +mkSpecName f k shape = + Name + ( nameToText f + <> "$sc" + <> Text.pack (show (k + 1)) + <> renderCtorName (ctorShapeCtor shape) + ) + +-------------------------------------------------------------------------------- +-- Call-site rewriting ---------------------------------------------------------- + +{- | Registered specializations, keyed by how call sites reference the +member they specialize, tried in ascending parameter position. +-} +type Actives = Map (Qualified Name) [Spec] + +activesOf ∷ [Spec] → Actives +activesOf specs = + Map.fromListWith (flip (<>)) [(specOriginRef s, [s]) | s ← specs] + +{- | Process one top-level right-hand side (or export): specialize the +local recursive groups it contains, splice the minted bindings into +their groups, and rewrite every qualifying call site — of the top-level +specializations and of this site's local ones alike. Local binder names +are unique per top-level site, so local candidates are collected and +rewritten independently per site (the discipline of +"Language.PureScript.Backend.IR.Uncurry"). +-} +processSite + ∷ Map (Qualified Name) Exp + → Actives + → Exp + → WriterT Any SupplyM Exp +processSite env topActives expr = do + localSpecs ← + lift . fmap (List.sortOn specPos . concat) $ + traverse + (groupSpecs env (const rootAnnVeto)) + [ [(Local name, name, rhs) | (_ann, name, rhs) ← toList localMembers] + | Let _ groupings _ ← toListOf (cosmosOf subexpressions) expr + , RecursiveGroup localMembers ← toList groupings + ] + let actives = Map.unionWith (<>) topActives (activesOf localSpecs) + -- A freshly minted local binding is spliced above the bottom-up + -- traversal's reach, so its own qualifying sites (the rewritten + -- self-call that makes the specialized loop allocation-free) are + -- rewritten here, before the splice. + splices ∷ Map Name [(Name, Exp)] + splices = + Map.fromListWith + (flip (<>)) + [ (name, [(specName s, fst (runWriter (rewriteSites env actives rhs)))]) + | s ← localSpecs + , Local name ← [specOriginRef s] + , Just rhs ← [specRHS s] + ] + tell (Any (any (isJust . specRHS) localSpecs)) + hoistWriter (spliceAndRewrite splices actives) + where + hoistWriter ∷ Writer Any a → WriterT Any SupplyM a + hoistWriter = writer . runWriter + + spliceAndRewrite ∷ Map Name [(Name, Exp)] → Actives → Writer Any Exp + spliceAndRewrite splices actives = + flip (transformMOf subexpressions) expr \e → do + e' ← rewriteSite env actives e + case e' of + Let ann groupings body + | any needsSplice (toList groupings) → + pure $ + Let ann (NE.fromList (spliceGrouping =<< toList groupings)) body + other → pure other + where + needsSplice = \case + RecursiveGroup localMembers → + any + (\(_ann, name, _rhs) → name `Map.member` splices) + (toList localMembers) + Standalone _ → False + + spliceGrouping = \case + g@(Standalone _) → [g] + RecursiveGroup localMembers → + [RecursiveGroup (NE.fromList (spliceMember =<< toList localMembers))] + + spliceMember member@(_ann, name, _rhs) = + [ (noAnn, sname, srhs) + | (sname, srhs) ← Map.findWithDefault [] name splices + ] + <> [member] + +-- | Rewrite every qualifying call site within the expression. +rewriteSites + ∷ Map (Qualified Name) Exp → Actives → Exp → Writer Any Exp +rewriteSites env actives = transformMOf subexpressions (rewriteSite env actives) + +{- | Rewrite one qualifying call site: a saturated call of a member with +a registered specialization, passing the pattern's constructor at the +pattern's position — the constructor node is unwrapped and its fields +passed directly. +-} +rewriteSite + ∷ Map (Qualified Name) Exp → Actives → Exp → Writer Any Exp +rewriteSite env actives e = case e of + AppN ann (Ref refAnn ref) args + | Just specs ← Map.lookup ref actives + , Just e' ← asum (matchSpec ann refAnn (toList args) <$> specs) → + e' <$ tell (Any True) + _ → pure e + where + matchSpec ∷ Ann → Ann → [Exp] → Spec → Maybe Exp + matchSpec ann refAnn args Spec {..} = do + guard (length args == specArity) + arg ← args !!? specPos + (shape, fields) ← resolveKnownCtorApp env arg + guard (shape == specShape && length fields == specFields) + newArgs ← + nonEmpty (take specPos args <> fields <> drop (specPos + 1) args) + pure (AppN ann (Ref refAnn specRef) newArgs) + +-------------------------------------------------------------------------------- +-- Helper Functions ------------------------------------------------------------- + +{- | Local names read through a constructor-eliminating position: a tag +or field read directly over the reference. +-} +eliminatedNames ∷ Exp → Set Name +eliminatedNames body = + Set.fromList + [ n + | e ← toListOf (cosmosOf subexpressions) body + , n ← case e of + ReflectCtor _ (Ref _ (Local n)) → [n] + DataArgumentByIndex _ _algTy _index (Ref _ (Local n)) → [n] + _ → [] + ] + +{- | Whether a right-hand side's root annotation vetoes specialization: +@inline never@ declares sharing intent for the binding as-is. +-} +rootAnnVeto ∷ Exp → Bool +rootAnnVeto rhs = getAnn rhs == Just Inliner.Never diff --git a/pslua.cabal b/pslua.cabal index 79fda699..82936c0c 100644 --- a/pslua.cabal +++ b/pslua.cabal @@ -141,6 +141,7 @@ library Language.PureScript.Backend.IR.Optimizer Language.PureScript.Backend.IR.Pass Language.PureScript.Backend.IR.Query + Language.PureScript.Backend.IR.SpecConstr Language.PureScript.Backend.IR.Supply Language.PureScript.Backend.IR.Types Language.PureScript.Backend.IR.Uncurry diff --git a/test/ps/output/Golden.SpecConstr.Test/corefn.json b/test/ps/output/Golden.SpecConstr.Test/corefn.json new file mode 100644 index 00000000..e4efb0cd --- /dev/null +++ b/test/ps/output/Golden.SpecConstr.Test/corefn.json @@ -0,0 +1 @@ +{"builtWith":"0.15.16","comments":[{"LineComment":" | Exercises call-pattern specialization (issue #208): a recursive"},{"LineComment":" | function that scrutinizes a parameter and passes a known"},{"LineComment":" | constructor at that position in its recursive calls gets a"},{"LineComment":" | specialized copy taking the constructor's fields as separate"},{"LineComment":" | parameters, so the hot loop carries raw values instead of"},{"LineComment":" | allocating a box per iteration. The eval oracle pins that every"},{"LineComment":" | shape keeps its runtime behavior, specialized or not."}],"decls":[{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[27,13],"start":[27,11]}},"type":"Var","value":{"identifier":"greaterThanOrEq","moduleName":["Data","Ord"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[27,15],"start":[27,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"ordInt","moduleName":["Data","Ord"]}},"type":"App"},"identifier":"greaterThanOrEq"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[28,36],"start":[28,35]}},"type":"Var","value":{"identifier":"add","moduleName":["Data","Semiring"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[28,38],"start":[28,33]}},"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":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[35,20],"start":[35,18]}},"type":"Var","value":{"identifier":"eq","moduleName":["Data","Eq"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[35,22],"start":[35,16]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"eqInt","moduleName":["Data","Eq"]}},"type":"App"},"identifier":"eq"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[35,55],"start":[35,54]}},"type":"Var","value":{"identifier":"sub","moduleName":["Data","Ring"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[35,57],"start":[35,52]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"ringInt","moduleName":["Data","Ring"]}},"type":"App"},"identifier":"sub"},{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"bindType":"NonRec","expression":{"abstraction":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[54,23],"start":[54,3]}},"type":"Var","value":{"identifier":"discard","moduleName":["Control","Bind"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[54,23],"start":[54,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":[54,23],"start":[54,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":[55,10],"start":[55,3]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Effect","Console"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[55,30],"start":[55,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":[21,33],"start":[21,1]}},"bindType":"NonRec","expression":{"annotation":{"meta":null,"sourceSpan":{"end":[21,33],"start":[21,1]}},"argument":"n","body":{"annotation":{"meta":{"metaType":"IsWhere"},"sourceSpan":{"end":[22,28],"start":[22,14]}},"binds":[{"bindType":"Rec","binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[24,39],"start":[24,3]}},"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[24,39],"start":[24,3]}},"argument":"acc","body":{"annotation":{"meta":null,"sourceSpan":{"end":[28,48],"start":[25,12]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[26,14],"start":[26,5]}},"binderType":"ConstructorBinder","binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[26,12],"start":[26,11]}},"binderType":"VarBinder","identifier":"s"},{"annotation":{"meta":null,"sourceSpan":{"end":[26,14],"start":[26,13]}},"binderType":"VarBinder","identifier":"i"}],"constructorName":{"identifier":"Tuple","moduleName":["Data","Tuple"]},"typeName":{"identifier":"Tuple","moduleName":["Data","Tuple"]}}],"expressions":[{"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[27,22],"start":[27,19]}},"type":"Var","value":{"identifier":"acc","sourcePos":[25,3]}},"guard":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"greaterThanOrEq","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[27,15],"start":[27,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[27,10],"start":[27,9]}},"type":"Var","value":{"identifier":"i","sourcePos":[26,13]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[27,15],"start":[27,9]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[27,15],"start":[27,14]}},"type":"Var","value":{"identifier":"n","sourcePos":[22,1]}},"type":"App"}},{"expression":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[28,24],"start":[28,22]}},"type":"Var","value":{"identifier":"go","sourcePos":[24,3]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,48],"start":[28,22]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[28,31],"start":[28,26]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,39],"start":[28,26]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"add","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,38],"start":[28,33]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,34],"start":[28,33]}},"type":"Var","value":{"identifier":"s","sourcePos":[26,11]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[28,38],"start":[28,33]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,38],"start":[28,37]}},"type":"Var","value":{"identifier":"i","sourcePos":[26,13]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[28,47],"start":[28,26]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"add","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[28,46],"start":[28,41]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,42],"start":[28,41]}},"type":"Var","value":{"identifier":"i","sourcePos":[26,13]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[28,46],"start":[28,41]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[28,46],"start":[28,45]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"type":"App"},"guard":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[28,18],"start":[28,9]}},"type":"Var","value":{"identifier":"otherwise","moduleName":["Data","Boolean"]}}}],"isGuarded":true}],"caseExpressions":[{"annotation":{"meta":null,"sourceSpan":{"end":[25,20],"start":[25,17]}},"type":"Var","value":{"identifier":"acc","sourcePos":[25,3]}}],"type":"Case"},"type":"Abs"},"identifier":"go"}]}],"expression":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[22,16],"start":[22,14]}},"type":"Var","value":{"identifier":"go","sourcePos":[24,3]}},"annotation":{"meta":null,"sourceSpan":{"end":[22,28],"start":[22,14]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[22,23],"start":[22,18]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[22,25],"start":[22,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[22,25],"start":[22,24]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[22,27],"start":[22,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[22,27],"start":[22,26]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"},"type":"App"},"type":"Let"},"type":"Abs"},"identifier":"sumCount"},{"bindType":"Rec","binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[32,29],"start":[32,1]}},"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[32,29],"start":[32,1]}},"argument":"m","body":{"annotation":{"meta":null,"sourceSpan":{"end":[35,59],"start":[33,14]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":{"constructorType":"SumType","identifiers":[],"metaType":"IsConstructor"},"sourceSpan":{"end":[34,10],"start":[34,3]}},"binderType":"ConstructorBinder","binders":[],"constructorName":{"identifier":"Nothing","moduleName":["Data","Maybe"]},"typeName":{"identifier":"Maybe","moduleName":["Data","Maybe"]}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[34,15],"start":[34,14]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"isGuarded":false},{"binders":[{"annotation":{"meta":{"constructorType":"SumType","identifiers":["value0"],"metaType":"IsConstructor"},"sourceSpan":{"end":[35,9],"start":[35,3]}},"binderType":"ConstructorBinder","binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[35,9],"start":[35,8]}},"binderType":"VarBinder","identifier":"i"}],"constructorName":{"identifier":"Just","moduleName":["Data","Maybe"]},"typeName":{"identifier":"Maybe","moduleName":["Data","Maybe"]}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[35,59],"start":[35,13]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[35,59],"start":[35,13]}},"binderType":"LiteralBinder","literal":{"literalType":"BooleanLiteral","value":true}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[35,30],"start":[35,28]}},"type":"Literal","value":{"literalType":"IntLiteral","value":42}},"isGuarded":false},{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[35,59],"start":[35,13]}},"binderType":"NullBinder"}],"expression":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[35,44],"start":[35,36]}},"type":"Var","value":{"identifier":"stepDown","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,59],"start":[35,36]}},"argument":{"abstraction":{"annotation":{"meta":{"constructorType":"SumType","identifiers":["value0"],"metaType":"IsConstructor"},"sourceSpan":{"end":[35,50],"start":[35,46]}},"type":"Var","value":{"identifier":"Just","moduleName":["Data","Maybe"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,58],"start":[35,46]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"sub","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,57],"start":[35,52]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,53],"start":[35,52]}},"type":"Var","value":{"identifier":"i","sourcePos":[35,8]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[35,57],"start":[35,52]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,57],"start":[35,56]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"type":"App"},"isGuarded":false}],"caseExpressions":[{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"eq","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[35,22],"start":[35,16]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,17],"start":[35,16]}},"type":"Var","value":{"identifier":"i","sourcePos":[35,8]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[35,22],"start":[35,16]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[35,22],"start":[35,21]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"}],"type":"Case"},"isGuarded":false}],"caseExpressions":[{"annotation":{"meta":null,"sourceSpan":{"end":[33,20],"start":[33,19]}},"type":"Var","value":{"identifier":"m","sourcePos":[33,1]}}],"type":"Case"},"type":"Abs"},"identifier":"stepDown"}]},{"bindType":"Rec","binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[43,29],"start":[43,1]}},"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[43,29],"start":[43,1]}},"argument":"t","body":{"annotation":{"meta":null,"sourceSpan":{"end":[45,66],"start":[44,10]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[45,12],"start":[45,3]}},"binderType":"ConstructorBinder","binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[45,10],"start":[45,9]}},"binderType":"VarBinder","identifier":"a"},{"annotation":{"meta":null,"sourceSpan":{"end":[45,12],"start":[45,11]}},"binderType":"VarBinder","identifier":"b"}],"constructorName":{"identifier":"Tuple","moduleName":["Data","Tuple"]},"typeName":{"identifier":"Tuple","moduleName":["Data","Tuple"]}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[45,66],"start":[45,16]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[45,66],"start":[45,16]}},"binderType":"LiteralBinder","literal":{"literalType":"BooleanLiteral","value":true}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[45,32],"start":[45,31]}},"type":"Var","value":{"identifier":"b","sourcePos":[45,11]}},"isGuarded":false},{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[45,66],"start":[45,16]}},"binderType":"NullBinder"}],"expression":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[45,42],"start":[45,38]}},"type":"Var","value":{"identifier":"ping","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,66],"start":[45,38]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[45,49],"start":[45,44]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,57],"start":[45,44]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"sub","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,56],"start":[45,51]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,52],"start":[45,51]}},"type":"Var","value":{"identifier":"a","sourcePos":[45,9]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,56],"start":[45,51]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,56],"start":[45,55]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,65],"start":[45,44]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"add","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,64],"start":[45,59]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,60],"start":[45,59]}},"type":"Var","value":{"identifier":"b","sourcePos":[45,11]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,64],"start":[45,59]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,64],"start":[45,63]}},"type":"Literal","value":{"literalType":"IntLiteral","value":2}},"type":"App"},"type":"App"},"type":"App"},"isGuarded":false}],"caseExpressions":[{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"eq","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[45,25],"start":[45,19]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,20],"start":[45,19]}},"type":"Var","value":{"identifier":"a","sourcePos":[45,9]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[45,25],"start":[45,19]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[45,25],"start":[45,24]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"}],"type":"Case"},"isGuarded":false}],"caseExpressions":[{"annotation":{"meta":null,"sourceSpan":{"end":[44,16],"start":[44,15]}},"type":"Var","value":{"identifier":"t","sourcePos":[44,1]}}],"type":"Case"},"type":"Abs"},"identifier":"pong"},{"annotation":{"meta":null,"sourceSpan":{"end":[39,29],"start":[39,1]}},"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[39,29],"start":[39,1]}},"argument":"t","body":{"annotation":{"meta":null,"sourceSpan":{"end":[41,66],"start":[40,10]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[41,12],"start":[41,3]}},"binderType":"ConstructorBinder","binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[41,10],"start":[41,9]}},"binderType":"VarBinder","identifier":"a"},{"annotation":{"meta":null,"sourceSpan":{"end":[41,12],"start":[41,11]}},"binderType":"VarBinder","identifier":"b"}],"constructorName":{"identifier":"Tuple","moduleName":["Data","Tuple"]},"typeName":{"identifier":"Tuple","moduleName":["Data","Tuple"]}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[41,66],"start":[41,16]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[41,66],"start":[41,16]}},"binderType":"LiteralBinder","literal":{"literalType":"BooleanLiteral","value":true}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[41,32],"start":[41,31]}},"type":"Var","value":{"identifier":"b","sourcePos":[41,11]}},"isGuarded":false},{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[41,66],"start":[41,16]}},"binderType":"NullBinder"}],"expression":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[41,42],"start":[41,38]}},"type":"Var","value":{"identifier":"pong","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,66],"start":[41,38]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[41,49],"start":[41,44]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,57],"start":[41,44]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"sub","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,56],"start":[41,51]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,52],"start":[41,51]}},"type":"Var","value":{"identifier":"a","sourcePos":[41,9]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[41,56],"start":[41,51]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,56],"start":[41,55]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[41,65],"start":[41,44]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"add","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,64],"start":[41,59]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,60],"start":[41,59]}},"type":"Var","value":{"identifier":"b","sourcePos":[41,11]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[41,64],"start":[41,59]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,64],"start":[41,63]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"type":"App"},"isGuarded":false}],"caseExpressions":[{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"eq","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[41,25],"start":[41,19]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,20],"start":[41,19]}},"type":"Var","value":{"identifier":"a","sourcePos":[41,9]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[41,25],"start":[41,19]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[41,25],"start":[41,24]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"}],"type":"Case"},"isGuarded":false}],"caseExpressions":[{"annotation":{"meta":null,"sourceSpan":{"end":[40,16],"start":[40,15]}},"type":"Var","value":{"identifier":"t","sourcePos":[40,1]}}],"type":"Case"},"type":"Abs"},"identifier":"ping"}]},{"bindType":"Rec","binds":[{"annotation":{"meta":null,"sourceSpan":{"end":[49,37],"start":[49,1]}},"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[49,37],"start":[49,1]}},"argument":"n","body":{"annotation":{"meta":null,"sourceSpan":{"end":[49,37],"start":[49,1]}},"argument":"t","body":{"annotation":{"meta":null,"sourceSpan":{"end":[50,60],"start":[50,13]}},"caseAlternatives":[{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[50,60],"start":[50,13]}},"binderType":"LiteralBinder","literal":{"literalType":"BooleanLiteral","value":true}}],"expression":{"annotation":{"meta":null,"sourceSpan":{"end":[50,29],"start":[50,28]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"isGuarded":false},{"binders":[{"annotation":{"meta":null,"sourceSpan":{"end":[50,60],"start":[50,13]}},"binderType":"NullBinder"}],"expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[50,40],"start":[50,35]}},"type":"Var","value":{"identifier":"blind","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,48],"start":[50,35]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"sub","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,47],"start":[50,42]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,43],"start":[50,42]}},"type":"Var","value":{"identifier":"n","sourcePos":[50,1]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[50,47],"start":[50,42]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,47],"start":[50,46]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[50,60],"start":[50,35]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[50,55],"start":[50,50]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,57],"start":[50,50]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,57],"start":[50,56]}},"type":"Var","value":{"identifier":"n","sourcePos":[50,1]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[50,59],"start":[50,50]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,59],"start":[50,58]}},"type":"Var","value":{"identifier":"n","sourcePos":[50,1]}},"type":"App"},"type":"App"},"isGuarded":false}],"caseExpressions":[{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"eq","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[50,22],"start":[50,16]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,17],"start":[50,16]}},"type":"Var","value":{"identifier":"n","sourcePos":[50,1]}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[50,22],"start":[50,16]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[50,22],"start":[50,21]}},"type":"Literal","value":{"literalType":"IntLiteral","value":0}},"type":"App"}],"type":"Case"},"type":"Abs"},"type":"Abs"},"identifier":"blind"}]},{"annotation":{"meta":null,"sourceSpan":{"end":[52,20],"start":[52,1]}},"bindType":"NonRec","expression":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"metaType":"IsForeign"},"sourceSpan":{"end":[54,10],"start":[54,3]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Effect","Console"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"showTuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"showInt","moduleName":["Data","Show"]}},"type":"App"},"annotation":{"meta":{"metaType":"IsSyntheticApp"},"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"showInt","moduleName":["Data","Show"]}},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[54,20],"start":[54,12]}},"type":"Var","value":{"identifier":"sumCount","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[54,22],"start":[54,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[54,22],"start":[54,21]}},"type":"Literal","value":{"literalType":"IntLiteral","value":5}},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[54,23],"start":[54,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[55,30],"start":[55,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[55,30],"start":[55,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[55,20],"start":[55,12]}},"type":"Var","value":{"identifier":"stepDown","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[55,29],"start":[55,12]}},"argument":{"abstraction":{"annotation":{"meta":{"constructorType":"SumType","identifiers":["value0"],"metaType":"IsConstructor"},"sourceSpan":{"end":[55,26],"start":[55,22]}},"type":"Var","value":{"identifier":"Just","moduleName":["Data","Maybe"]}},"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,22]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[55,28],"start":[55,27]}},"type":"Literal","value":{"literalType":"IntLiteral","value":3}},"type":"App"},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[55,30],"start":[55,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[55,30],"start":[55,3]}},"argument":"$__unused","body":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"discard","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[56,29],"start":[56,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[56,29],"start":[56,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[56,20],"start":[56,12]}},"type":"Var","value":{"identifier":"stepDown","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[56,28],"start":[56,12]}},"argument":{"annotation":{"meta":{"constructorType":"SumType","identifiers":[],"metaType":"IsConstructor"},"sourceSpan":{"end":[56,28],"start":[56,21]}},"type":"Var","value":{"identifier":"Nothing","moduleName":["Data","Maybe"]}},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[56,29],"start":[56,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[56,29],"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","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[57,30],"start":[57,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[57,30],"start":[57,3]}},"argument":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[57,16],"start":[57,12]}},"type":"Var","value":{"identifier":"ping","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[57,29],"start":[57,12]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[57,23],"start":[57,18]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[57,25],"start":[57,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[57,25],"start":[57,24]}},"type":"Literal","value":{"literalType":"IntLiteral","value":4}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[57,28],"start":[57,18]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[57,28],"start":[57,26]}},"type":"Literal","value":{"literalType":"IntLiteral","value":10}},"type":"App"},"type":"App"},"type":"App"},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[57,30],"start":[57,3]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[57,30],"start":[57,3]}},"argument":"$__unused","body":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[0,0],"start":[0,0]}},"type":"Var","value":{"identifier":"logShow","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[58,3]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":null,"sourceSpan":{"end":[58,17],"start":[58,12]}},"type":"Var","value":{"identifier":"blind","moduleName":["Golden","SpecConstr","Test"]}},"annotation":{"meta":null,"sourceSpan":{"end":[58,19],"start":[58,12]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[58,19],"start":[58,18]}},"type":"Literal","value":{"literalType":"IntLiteral","value":3}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[58,31],"start":[58,12]}},"argument":{"abstraction":{"abstraction":{"annotation":{"meta":{"constructorType":"ProductType","identifiers":["value0","value1"],"metaType":"IsConstructor"},"sourceSpan":{"end":[58,26],"start":[58,21]}},"type":"Var","value":{"identifier":"Tuple","moduleName":["Data","Tuple"]}},"annotation":{"meta":null,"sourceSpan":{"end":[58,28],"start":[58,21]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[58,28],"start":[58,27]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"annotation":{"meta":null,"sourceSpan":{"end":[58,30],"start":[58,21]}},"argument":{"annotation":{"meta":null,"sourceSpan":{"end":[58,30],"start":[58,29]}},"type":"Literal","value":{"literalType":"IntLiteral","value":1}},"type":"App"},"type":"App"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"type":"Abs"},"type":"App"},"identifier":"main"}],"exports":["sumCount","stepDown","ping","pong","blind","main"],"foreign":[],"imports":[{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Control","Bind"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Boolean"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Eq"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Maybe"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Ord"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Ring"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Semiring"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Show"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Data","Tuple"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Effect"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Effect","Console"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Golden","SpecConstr","Test"]},{"annotation":{"meta":null,"sourceSpan":{"end":[10,15],"start":[10,1]}},"moduleName":["Prelude"]},{"annotation":{"meta":null,"sourceSpan":{"end":[58,32],"start":[8,1]}},"moduleName":["Prim"]}],"moduleName":["Golden","SpecConstr","Test"],"modulePath":"src/Golden/SpecConstr/Test.purs","reExports":{},"sourceSpan":{"end":[58,32],"start":[8,1]}} \ No newline at end of file diff --git a/test/ps/output/Golden.SpecConstr.Test/eval/.gitignore b/test/ps/output/Golden.SpecConstr.Test/eval/.gitignore new file mode 100644 index 00000000..d2dc29bb --- /dev/null +++ b/test/ps/output/Golden.SpecConstr.Test/eval/.gitignore @@ -0,0 +1 @@ +actual.txt diff --git a/test/ps/output/Golden.SpecConstr.Test/eval/golden.txt b/test/ps/output/Golden.SpecConstr.Test/eval/golden.txt new file mode 100644 index 00000000..7ef3f1dd --- /dev/null +++ b/test/ps/output/Golden.SpecConstr.Test/eval/golden.txt @@ -0,0 +1,5 @@ +(Tuple 10 5) +42 +0 +16 +0 diff --git a/test/ps/output/Golden.SpecConstr.Test/golden.ir b/test/ps/output/Golden.SpecConstr.Test/golden.ir new file mode 100644 index 00000000..3751721a --- /dev/null +++ b/test/ps/output/Golden.SpecConstr.Test/golden.ir @@ -0,0 +1,608 @@ +UberModule + { uberModuleBindings = + [ Standalone + ( QName + { qnameModuleName = ModuleName "Data.Show", qnameName = Name "foreign" + }, ForeignImport Nothing + ( ModuleName "Data.Show" ) ".spago/p/prelude/5718c84fdde6247749cb053e816df696c30fe691/src/Data/Show.purs" + [ ( Nothing, Name "showIntImpl" ) ] + ), Standalone + ( QName + { qnameModuleName = ModuleName "Data.Show", qnameName = Name "showIntImpl" + }, ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Show" ) ( Name "foreign" ) ) ) + ( PropName "showIntImpl" ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Effect.Console", qnameName = Name "foreign" + }, ForeignImport Nothing + ( ModuleName "Effect.Console" ) ".spago/p/console/f82835a0b873aafe6bd7b14dd30cc150553d4ab9/src/Effect/Console.purs" + [ ( Nothing, Name "log" ) ] + ), Standalone + ( QName + { qnameModuleName = ModuleName "Data.Show", qnameName = Name "showInt" + }, LiteralObject Nothing + [ + ( PropName "show", Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) + ) + ] + ), Standalone + ( QName + { qnameModuleName = ModuleName "Data.Tuple", qnameName = Name "append$w" }, AbsN Nothing + ( ParamNamed Nothing ( Name "s1$403" ) :| [ ParamNamed Nothing ( Name "s2$404" ) ] ) + ( PrimBinOp Nothing PrimConcat + ( Ref Nothing ( Local ( Name "s1$403" ) ) ) + ( Ref Nothing ( Local ( Name "s2$404" ) ) ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Data.Tuple", qnameName = Name "Tuple$w" }, AbsN Nothing + ( ParamNamed Nothing ( Name "value0" ) :| [ ParamNamed Nothing ( Name "value1" ) ] ) + ( Ctor Nothing ProductType + ( ModuleName "Data.Tuple" ) + ( TyName "Tuple" ) + ( CtorName "Tuple" ) + [ Ref Nothing ( Local ( Name "value0" ) ), Ref Nothing ( Local ( Name "value1" ) ) ] + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Effect.Console", qnameName = Name "logShow$w" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "dictShow" ) :| [ ParamNamed Nothing ( Name "a" ) ] ) + ( AppN Nothing + ( ObjectProp Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "foreign" ) ) ) + ( PropName "log" ) + ) + ( AppN Nothing + ( ObjectProp Nothing + ( Ref Nothing ( Local ( Name "dictShow" ) ) ) + ( PropName "show" ) + ) + ( Ref Nothing ( Local ( Name "a" ) ) :| [] ) :| [] + ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "sub$w" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "x$398" ) :| [ ParamNamed Nothing ( Name "y$399" ) ] ) + ( PrimBinOp Nothing PrimSub + ( Ref Nothing ( Local ( Name "x$398" ) ) ) + ( Ref Nothing ( Local ( Name "y$399" ) ) ) + ) + ), Standalone + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "sumCount" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "n" ) :| [] ) + ( Let Nothing + ( RecursiveGroup + ( + ( Nothing, Name "go$sc1Tuple", AbsN Nothing + ( ParamNamed Nothing + ( Name "go$sc1Tuple$f1" ) :| + [ ParamNamed Nothing ( Name "go$sc1Tuple$f2" ) ] + ) + ( IfThenElse Nothing + ( PrimBinOp Nothing PrimLt + ( Ref Nothing ( Local ( Name "go$sc1Tuple$f2" ) ) ) + ( Ref Nothing ( Local ( Name "n" ) ) ) + ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "go$sc1Tuple" ) ) ) + ( PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "go$sc1Tuple$f1" ) ) ) + ( Ref Nothing ( Local ( Name "go$sc1Tuple$f2" ) ) ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "go$sc1Tuple$f2" ) ) ) + ( LiteralInt Nothing 1 ) + ] + ) + ) + ( Ctor Nothing ProductType + ( ModuleName "Data.Tuple" ) + ( TyName "Tuple" ) + ( CtorName "Tuple" ) + [ Ref Nothing + ( Local ( Name "go$sc1Tuple$f1" ) ), Ref Nothing + ( Local ( Name "go$sc1Tuple$f2" ) ) + ] + ) + ) + ) :| [] + ) :| [] + ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "go$sc1Tuple" ) ) ) + ( LiteralInt Nothing 0 :| [ LiteralInt Nothing 0 ] ) + ) + ) + ), RecursiveGroup + ( + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "stepDown$sc1Just" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "stepDown$sc1Just$f1" ) :| [] ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "stepDown$sc1Just$f1" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( LiteralInt Nothing 42 ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "stepDown$sc1Just" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "stepDown$sc1Just$f1" ) ) :| + [ LiteralInt Nothing 1 ] + ) :| [] + ) + ) + ) + ) :| + [ + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "stepDown" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "m" ) :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse449", DataArgumentByIndex Nothing SumType 0 + ( Ref Nothing ( Local ( Name "m" ) ) ) + ) :| [] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse448", ReflectCtor Nothing + ( Ref Nothing ( Local ( Name "m" ) ) ) + ) :| [] + ) + ( IfThenElse Nothing + ( Eq Nothing + ( LiteralString Nothing "Data.Maybe∷Maybe.Nothing" ) + ( Ref Nothing ( Local ( Name "$cse448" ) ) ) + ) + ( LiteralInt Nothing 0 ) + ( IfThenElse Nothing + ( Eq Nothing + ( LiteralString Nothing "Data.Maybe∷Maybe.Just" ) + ( Ref Nothing ( Local ( Name "$cse448" ) ) ) + ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "$cse449" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( LiteralInt Nothing 42 ) + ( AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Golden.SpecConstr.Test" ) + ( Name "stepDown$sc1Just" ) + ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "$cse449" ) ) :| + [ LiteralInt Nothing 1 ] + ) :| [] + ) + ) + ) + ( Exception Nothing "No patterns matched" ) + ) + ) + ) + ) + ) + ] + ), RecursiveGroup + ( + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "pong$sc1Tuple" + }, AbsN Nothing + ( ParamNamed Nothing + ( Name "pong$sc1Tuple$f1" ) :| + [ ParamNamed Nothing ( Name "pong$sc1Tuple$f2" ) ] + ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "pong$sc1Tuple$f1" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( Ref Nothing ( Local ( Name "pong$sc1Tuple$f2" ) ) ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "ping$sc1Tuple" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "pong$sc1Tuple$f1" ) ) :| + [ LiteralInt Nothing 1 ] + ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "pong$sc1Tuple$f2" ) ) ) + ( LiteralInt Nothing 2 ) + ] + ) + ) + ) + ) :| + [ + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "pong" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "t" ) :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse451", DataArgumentByIndex Nothing ProductType 1 + ( Ref Nothing ( Local ( Name "t" ) ) ) + ) :| [] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse450", DataArgumentByIndex Nothing ProductType 0 + ( Ref Nothing ( Local ( Name "t" ) ) ) + ) :| [] + ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "$cse450" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( Ref Nothing ( Local ( Name "$cse451" ) ) ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "ping$sc1Tuple" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing ( Local ( Name "$cse450" ) ) :| [ LiteralInt Nothing 1 ] ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "$cse451" ) ) ) + ( LiteralInt Nothing 2 ) + ] + ) + ) + ) + ) + ) + ), + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "ping$sc1Tuple" + }, AbsN Nothing + ( ParamNamed Nothing + ( Name "ping$sc1Tuple$f1" ) :| + [ ParamNamed Nothing ( Name "ping$sc1Tuple$f2" ) ] + ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "ping$sc1Tuple$f1" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( Ref Nothing ( Local ( Name "ping$sc1Tuple$f2" ) ) ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "pong$sc1Tuple" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "ping$sc1Tuple$f1" ) ) :| + [ LiteralInt Nothing 1 ] + ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "ping$sc1Tuple$f2" ) ) ) + ( LiteralInt Nothing 1 ) + ] + ) + ) + ) + ), + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "ping" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "t" ) :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse453", DataArgumentByIndex Nothing ProductType 1 + ( Ref Nothing ( Local ( Name "t" ) ) ) + ) :| [] + ) + ( Let Nothing + ( Standalone + ( Nothing, Name "$cse452", DataArgumentByIndex Nothing ProductType 0 + ( Ref Nothing ( Local ( Name "t" ) ) ) + ) :| [] + ) + ( IfThenElse Nothing + ( Eq Nothing + ( Ref Nothing ( Local ( Name "$cse452" ) ) ) + ( LiteralInt Nothing 0 ) + ) + ( Ref Nothing ( Local ( Name "$cse453" ) ) ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "pong$sc1Tuple" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing ( Local ( Name "$cse452" ) ) :| [ LiteralInt Nothing 1 ] ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "$cse453" ) ) ) + ( LiteralInt Nothing 1 ) + ] + ) + ) + ) + ) + ) + ) + ] + ), RecursiveGroup + ( + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "blind$w" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "n" ) :| [ ParamUnused Nothing ] ) + ( IfThenElse Nothing + ( Eq Nothing ( Ref Nothing ( Local ( Name "n" ) ) ) ( LiteralInt Nothing 0 ) ) + ( LiteralInt Nothing 0 ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "blind$w" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sub$w" ) ) + ) + ( Ref Nothing ( Local ( Name "n" ) ) :| [ LiteralInt Nothing 1 ] ) :| + [ AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Tuple" ) ( Name "Tuple$w" ) ) ) + ( Ref Nothing ( Local ( Name "n" ) ) :| [ Ref Nothing ( Local ( Name "n" ) ) ] ) + ] + ) + ) + ) + ) :| + [ + ( QName + { qnameModuleName = ModuleName "Golden.SpecConstr.Test", qnameName = Name "blind" + }, AbsN Nothing + ( ParamNamed Nothing ( Name "blind$p1" ) :| [] ) + ( AbsN Nothing + ( ParamNamed Nothing ( Name "blind$p2" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "blind$w" ) ) + ) + ( Ref Nothing + ( Local ( Name "blind$p1" ) ) :| + [ Ref Nothing ( Local ( Name "blind$p2" ) ) ] + ) + ) + ) + ) + ] + ) + ], uberModuleForeigns = [], uberModuleExports = + [ + ( Name "sumCount", Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "sumCount" ) ) + ), + ( Name "stepDown", Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "stepDown" ) ) + ), + ( Name "ping", Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "ping" ) ) + ), + ( Name "pong", Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "pong" ) ) + ), + ( Name "blind", Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "blind" ) ) + ), + ( Name "main", AbsN Nothing + ( ParamUnused Nothing :| [] ) + ( Let Nothing + ( Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "logShow$w" ) ) ) + ( LiteralObject Nothing + [ + ( PropName "show", AbsN Nothing + ( ParamNamed Nothing ( Name "v$86" ) :| [] ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Tuple" ) ( Name "append$w" ) ) + ) + ( LiteralString Nothing "(Tuple " :| + [ AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Tuple" ) ( Name "append$w" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showIntImpl" ) ) + ) + ( DataArgumentByIndex Nothing ProductType 0 + ( Ref Nothing ( Local ( Name "v$86" ) ) ) :| [] + ) :| + [ AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Tuple" ) ( Name "append$w" ) ) + ) + ( LiteralString Nothing " " :| + [ AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Data.Tuple" ) ( Name "append$w" ) ) + ) + ( AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Data.Show" ) + ( Name "showIntImpl" ) + ) + ) + ( DataArgumentByIndex Nothing ProductType 1 + ( Ref Nothing ( Local ( Name "v$86" ) ) ) :| [] + ) :| + [ LiteralString Nothing ")" ] + ) + ] + ) + ] + ) + ] + ) + ) + ) + ] :| + [ Let Nothing + ( RecursiveGroup + ( + ( Nothing, Name "go$432$sc1Tuple", AbsN Nothing + ( ParamNamed Nothing + ( Name "go$432$sc1Tuple$f1" ) :| + [ ParamNamed Nothing ( Name "go$432$sc1Tuple$f2" ) ] + ) + ( IfThenElse Nothing + ( PrimBinOp Nothing PrimLt + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple$f2" ) ) ) + ( LiteralInt Nothing 5 ) + ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple" ) ) ) + ( PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple$f1" ) ) ) + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple$f2" ) ) ) :| + [ PrimBinOp Nothing PrimAdd + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple$f2" ) ) ) + ( LiteralInt Nothing 1 ) + ] + ) + ) + ( Ctor Nothing ProductType + ( ModuleName "Data.Tuple" ) + ( TyName "Tuple" ) + ( CtorName "Tuple" ) + [ Ref Nothing + ( Local ( Name "go$432$sc1Tuple$f1" ) ), Ref Nothing + ( Local ( Name "go$432$sc1Tuple$f2" ) ) + ] + ) + ) + ) :| [] + ) :| [] + ) + ( AppN Nothing + ( Ref Nothing ( Local ( Name "go$432$sc1Tuple" ) ) ) + ( LiteralInt Nothing 0 :| [ LiteralInt Nothing 0 ] ) + ) + ] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) :| + [ Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "logShow$w" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showInt" ) ) :| + [ AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Golden.SpecConstr.Test" ) + ( Name "stepDown$sc1Just" ) + ) + ) + ( LiteralInt Nothing 3 :| [] ) + ] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ), Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "logShow$w" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showInt" ) ) :| + [ AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "stepDown" ) ) + ) + ( Ctor Nothing SumType + ( ModuleName "Data.Maybe" ) + ( TyName "Maybe" ) + ( CtorName "Nothing" ) [] :| [] + ) + ] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ), Standalone + ( Nothing, Name "_", AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "logShow$w" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showInt" ) ) :| + [ AppN Nothing + ( Ref Nothing + ( Imported + ( ModuleName "Golden.SpecConstr.Test" ) + ( Name "ping$sc1Tuple" ) + ) + ) + ( LiteralInt Nothing 4 :| [ LiteralInt Nothing 10 ] ) + ] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ] + ) + ( AppN Nothing + ( AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Effect.Console" ) ( Name "logShow$w" ) ) ) + ( Ref Nothing + ( Imported ( ModuleName "Data.Show" ) ( Name "showInt" ) ) :| + [ AppN Nothing + ( Ref Nothing + ( Imported ( ModuleName "Golden.SpecConstr.Test" ) ( Name "blind$w" ) ) + ) + ( LiteralInt Nothing 3 :| + [ AppN Nothing + ( Ref Nothing ( Imported ( ModuleName "Data.Tuple" ) ( Name "Tuple$w" ) ) ) + ( LiteralInt Nothing 1 :| [ LiteralInt Nothing 1 ] ) + ] + ) + ] + ) + ) + ( Ref Nothing ( Imported ( ModuleName "Prim" ) ( Name "$magicDoRun" ) ) :| [] ) + ) + ) + ) + ] + } \ No newline at end of file diff --git a/test/ps/output/Golden.SpecConstr.Test/golden.lua b/test/ps/output/Golden.SpecConstr.Test/golden.lua new file mode 100644 index 00000000..cafc8cac --- /dev/null +++ b/test/ps/output/Golden.SpecConstr.Test/golden.lua @@ -0,0 +1,130 @@ +local M = {} +local Data_Show_foreign = { showIntImpl = function(n) return tostring(n) end } +local Data_Show_showIntImpl = Data_Show_foreign.showIntImpl +local Effect_Console_foreign = { + log = function(s) return function() print(s) end end +} +local Data_Show_showInt = { show = Data_Show_showIntImpl } +local Data_Tuple_append_S_w = function(s1_S_403, s2_S_404) + return s1_S_403 .. s2_S_404 +end +local Data_Tuple_Tuple_S_w = function(value0, value1) + return { value0, value1 } +end +local Effect_Console_logShow_S_w = function(dictShow, a) + return Effect_Console_foreign.log(dictShow.show(a)) +end +local Golden_SpecConstr_Test_sub_S_w = function(x_S_398, y_S_399) + return x_S_398 - y_S_399 +end +M.Golden_SpecConstr_Test_sumCount = function(n) + local go_S_sc1Tuple + go_S_sc1Tuple = function(go_S_sc1Tuple_S_f1, go_S_sc1Tuple_S_f2) + while true do + if go_S_sc1Tuple_S_f2 < n then + go_S_sc1Tuple_S_f1, go_S_sc1Tuple_S_f2 = go_S_sc1Tuple_S_f1 + go_S_sc1Tuple_S_f2, go_S_sc1Tuple_S_f2 + 1 + else + return { go_S_sc1Tuple_S_f1, go_S_sc1Tuple_S_f2 } + end + end + end + return go_S_sc1Tuple(0, 0) +end +local Golden_SpecConstr_Test_stepDown_S_sc1Just = function( stepDown_S_sc1Just_S_f1 ) + while true do + if stepDown_S_sc1Just_S_f1 == 0 then + return 42 + else + stepDown_S_sc1Just_S_f1 = Golden_SpecConstr_Test_sub_S_w(stepDown_S_sc1Just_S_f1, 1) + end + end +end +local Golden_SpecConstr_Test_stepDown = function(m) + local _S_cse449 = m[2] + local _S_cse448 = m[1] + if "Data.Maybe∷Maybe.Nothing" == _S_cse448 then + return 0 + elseif "Data.Maybe∷Maybe.Just" == _S_cse448 then + if _S_cse449 == 0 then + return 42 + else + return Golden_SpecConstr_Test_stepDown_S_sc1Just(Golden_SpecConstr_Test_sub_S_w(_S_cse449, 1)) + end + else + return error("No patterns matched") + end +end +local Golden_SpecConstr_Test_ping_S_sc1Tuple +local Golden_SpecConstr_Test_pong_S_sc1Tuple = function( pong_S_sc1Tuple_S_f1 +, pong_S_sc1Tuple_S_f2 ) + if pong_S_sc1Tuple_S_f1 == 0 then + return pong_S_sc1Tuple_S_f2 + else + return Golden_SpecConstr_Test_ping_S_sc1Tuple(Golden_SpecConstr_Test_sub_S_w(pong_S_sc1Tuple_S_f1, 1), pong_S_sc1Tuple_S_f2 + 2) + end +end +M.Golden_SpecConstr_Test_pong = function(t) + local _S_cse451 = t[2] + local _S_cse450 = t[1] + if _S_cse450 == 0 then + return _S_cse451 + else + return Golden_SpecConstr_Test_ping_S_sc1Tuple(Golden_SpecConstr_Test_sub_S_w(_S_cse450, 1), _S_cse451 + 2) + end +end +Golden_SpecConstr_Test_ping_S_sc1Tuple = function( ping_S_sc1Tuple_S_f1 +, ping_S_sc1Tuple_S_f2 ) + if ping_S_sc1Tuple_S_f1 == 0 then + return ping_S_sc1Tuple_S_f2 + else + return Golden_SpecConstr_Test_pong_S_sc1Tuple(Golden_SpecConstr_Test_sub_S_w(ping_S_sc1Tuple_S_f1, 1), ping_S_sc1Tuple_S_f2 + 1) + end +end +M.Golden_SpecConstr_Test_ping = function(t) + local _S_cse453 = t[2] + local _S_cse452 = t[1] + if _S_cse452 == 0 then + return _S_cse453 + else + return Golden_SpecConstr_Test_pong_S_sc1Tuple(Golden_SpecConstr_Test_sub_S_w(_S_cse452, 1), _S_cse453 + 1) + end +end +local Golden_SpecConstr_Test_blind_S_w +Golden_SpecConstr_Test_blind_S_w = function(n) + if n == 0 then + return 0 + else + return Golden_SpecConstr_Test_blind_S_w(Golden_SpecConstr_Test_sub_S_w(n, 1), Data_Tuple_Tuple_S_w(n, n)) + end +end +M.Golden_SpecConstr_Test_blind = function(blind_S_p1) + return function(blind_S_p2) + return Golden_SpecConstr_Test_blind_S_w(blind_S_p1, blind_S_p2) + end +end +return (function() + local _ = Effect_Console_logShow_S_w({ + show = function(v_S_86) + return Data_Tuple_append_S_w("(Tuple ", Data_Tuple_append_S_w(Data_Show_showIntImpl(v_S_86[1]), Data_Tuple_append_S_w(" ", Data_Tuple_append_S_w(Data_Show_showIntImpl(v_S_86[2]), ")")))) + end + }, (function() + local go_S_432_S_sc1Tuple + go_S_432_S_sc1Tuple = function( go_S_432_S_sc1Tuple_S_f1 + , go_S_432_S_sc1Tuple_S_f2 ) + while true do + if go_S_432_S_sc1Tuple_S_f2 < 5 then + go_S_432_S_sc1Tuple_S_f1, go_S_432_S_sc1Tuple_S_f2 = go_S_432_S_sc1Tuple_S_f1 + go_S_432_S_sc1Tuple_S_f2, go_S_432_S_sc1Tuple_S_f2 + 1 + else + return { go_S_432_S_sc1Tuple_S_f1, go_S_432_S_sc1Tuple_S_f2 } + end + end + end + return go_S_432_S_sc1Tuple(0, 0) + end)())() + local _ = Effect_Console_logShow_S_w(Data_Show_showInt, Golden_SpecConstr_Test_stepDown_S_sc1Just(3))() + local _ = Effect_Console_logShow_S_w(Data_Show_showInt, Golden_SpecConstr_Test_stepDown({ + "Data.Maybe∷Maybe.Nothing" + }))() + local _ = Effect_Console_logShow_S_w(Data_Show_showInt, Golden_SpecConstr_Test_ping_S_sc1Tuple(4, 10))() + return Effect_Console_logShow_S_w(Data_Show_showInt, Golden_SpecConstr_Test_blind_S_w(3, Data_Tuple_Tuple_S_w(1, 1)))() +end)() diff --git a/test/ps/src/Bench/TupleFold.purs b/test/ps/src/Bench/TupleFold.purs new file mode 100644 index 00000000..7b620933 --- /dev/null +++ b/test/ps/src/Bench/TupleFold.purs @@ -0,0 +1,21 @@ +-- | A fold-shaped hot loop carrying a `Tuple` accumulator — the shape +-- | call-pattern specialization (issue #208) unboxes: without it every +-- | iteration builds a fresh two-field table only for the next +-- | iteration's match to take it apart; with it the loop carries the +-- | two fields as raw parameters and the box materializes only on the +-- | exit path. +module Bench.TupleFold where + +import Prelude + +import Data.Tuple (Tuple(..)) + +run :: Int -> Int +run n = case go (Tuple 0 0) of + Tuple s _ -> s + where + go :: Tuple Int Int -> Tuple Int Int + go acc = case acc of + Tuple s i + | i >= n -> acc + | otherwise -> go (Tuple (s + i) (i + 1)) diff --git a/test/ps/src/Golden/SpecConstr/Test.purs b/test/ps/src/Golden/SpecConstr/Test.purs new file mode 100644 index 00000000..7d5870a3 --- /dev/null +++ b/test/ps/src/Golden/SpecConstr/Test.purs @@ -0,0 +1,58 @@ +-- | Exercises call-pattern specialization (issue #208): a recursive +-- | function that scrutinizes a parameter and passes a known +-- | constructor at that position in its recursive calls gets a +-- | specialized copy taking the constructor's fields as separate +-- | parameters, so the hot loop carries raw values instead of +-- | allocating a box per iteration. The eval oracle pins that every +-- | shape keeps its runtime behavior, specialized or not. +module Golden.SpecConstr.Test where + +import Prelude + +import Data.Maybe (Maybe(..)) +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Console (logShow) + +-- The canonical fold: a product-type accumulator built afresh on every +-- iteration and taken apart at the top of the next one. Specialization +-- carries the two fields as loop parameters; the box materializes only +-- on the exit path. +sumCount :: Int -> Tuple Int Int +sumCount n = go (Tuple 0 0) + where + go :: Tuple Int Int -> Tuple Int Int + go acc = case acc of + Tuple s i + | i >= n -> acc + | otherwise -> go (Tuple (s + i) (i + 1)) + +-- A sum-type accumulator: only the `Just` pattern recurs, so only it +-- is specialized; the `Nothing` arm stays on the boxed entry path. +stepDown :: Maybe Int -> Int +stepDown m = case m of + Nothing -> 0 + Just i -> if i == 0 then 42 else stepDown (Just (i - 1)) + +-- Mutual recursion: each member's call sites live in the other's body, +-- and both carry the same constructor pattern. +ping :: Tuple Int Int -> Int +ping t = case t of + Tuple a b -> if a == 0 then b else pong (Tuple (a - 1) (b + 1)) + +pong :: Tuple Int Int -> Int +pong t = case t of + Tuple a b -> if a == 0 then b else ping (Tuple (a - 1) (b + 2)) + +-- Negative control: the boxed parameter is never scrutinized (it is +-- dead), so there is nothing to gain and the binding is left alone. +blind :: Int -> Tuple Int Int -> Int +blind n t = if n == 0 then 0 else blind (n - 1) (Tuple n n) + +main :: Effect Unit +main = do + logShow (sumCount 5) + logShow (stepDown (Just 3)) + logShow (stepDown Nothing) + logShow (ping (Tuple 4 10)) + logShow (blind 3 (Tuple 1 1))