clash-lib-1.10.1: src/Clash/Rewrite/Combinators.hs
{-|
Copyright : (C) 2012-2016, University of Twente
2021-2026, QBayLogic B.V.
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
Rewriting combinators and traversals
-}
module Clash.Rewrite.Combinators
( allR
, (!->)
, (>-!)
, (>-!->)
, (>->)
, bottomupR
, repeatR
, topdownR
, topdownFixR
) where
import Control.DeepSeq (deepseq)
import Control.Monad ((>=>))
import qualified Control.Monad.Writer as Writer
import qualified Data.Monoid as Monoid
import Clash.Core.Term (Term (..), CoreContext (..), primArg, patIds)
import Clash.Core.VarEnv
(extendInScopeSet, extendInScopeSetList)
import Clash.Rewrite.Types
-- | Apply a transformation on the subtrees of an term
allR
:: forall m
. Monad m
=> Transform m
-- ^ The transformation to apply to the subtrees
-> Transform m
allR trans (TransformContext is c) (Lam v e) =
Lam v <$> trans (TransformContext (extendInScopeSet is v) (LamBody v:c)) e
allR trans (TransformContext is c) (TyLam tv e) =
TyLam tv <$> trans (TransformContext (extendInScopeSet is tv) (TyLamBody tv:c)) e
allR trans (TransformContext is c) (App e1 e2) = do
e1' <- trans (TransformContext is (AppFun:c)) e1
e2' <- trans (TransformContext is (AppArg (primArg e1') : c)) e2
pure (App e1' e2')
allR trans (TransformContext is c) (TyApp e ty) =
TyApp <$> trans (TransformContext is (TyAppC:c)) e <*> pure ty
allR trans (TransformContext is c) (Cast e ty1 ty2) =
Cast <$> trans (TransformContext is (CastBody:c)) e <*> pure ty1 <*> pure ty2
allR trans (TransformContext is c) (Letrec xes e) = do
xes' <- traverse rewriteBind xes
e' <- trans (TransformContext is' (LetBody xes:c)) e
return (Letrec xes' e')
where
bndrs = map fst xes
is' = extendInScopeSetList is (map fst xes)
rewriteBind (b,e') = (b,) <$> trans (TransformContext is' (LetBinding b bndrs:c)) e'
allR trans (TransformContext is c) (Case scrut ty alts) =
Case <$> trans (TransformContext is (CaseScrut:c)) scrut
<*> pure ty
<*> traverse rewriteAlt alts
where
rewriteAlt (p,e) =
let (tvs,ids) = patIds p
is' = extendInScopeSetList (extendInScopeSetList is tvs) ids
in (p,) <$> trans (TransformContext is' (CaseAlt p : c)) e
allR trans (TransformContext is c) (Tick sp e) =
Tick sp <$> trans (TransformContext is (TickC sp:c)) e
allR _ _ tm = pure tm
infixr 6 >->
-- | Apply two transformations in succession
(>->) :: Monad m => Transform m -> Transform m -> Transform m
(>->) = \r1 r2 c -> r1 c >=> r2 c
{-# INLINE (>->) #-}
infixr 6 >-!->
-- | Apply two transformations in succession, and perform a deepseq in between.
(>-!->) :: Monad m => Transform m -> Transform m -> Transform m
(>-!->) = \r1 r2 c e -> do
e' <- r1 c e
deepseq e' (r2 c e')
{-# INLINE (>-!->) #-}
{-
Note [topdown repeatR]
~~~~~~~~~~~~~~~~~~~~~~
In a topdown traversal we need to repeat the transformation r because
if r replaces a parent node with one of its children
we should still apply r to that child, before continuing with its children.
Example: topdownR (inlineBinders (\_ _ -> return True))
on:
> letrec
> x = 1
> in letrec
> y = 2
> in f x y
inlineBinders would inline x and return:
> letrec
> y = 2
> in f 1 y
Then we must repeat the transformation to let it also inline y.
-}
-- | Apply a transformation in a topdown traversal
topdownR :: Rewrite m -> Rewrite m
-- See Note [topdown repeatR]
topdownR r = repeatR r >-> allR (topdownR r)
{-
Note [topdownFixR]
~~~~~~~~~~~~~~~~~~
'topdownFixR r' is an optimized alternative to some uses of
'repeatR (topdownR r)'. It repeats 'r' top-down, but when a child changes it
only rechecks the ancestors of that child instead of restarting traversal from
the root.
For example, suppose 'r' can rewrite both:
> let x = True in x
to:
> True
and:
> case True of { True -> a; False -> b }
to:
> a
When traversing:
> h (case (let x = True in x) of { True -> a; False -> b })
'topdownFixR r' first cannot rewrite the 'case', so it descends into the
scrutinee. Rewriting the scrutinee exposes a new redex at the parent 'case', so
the parent is checked again immediately and rewritten to 'a'. That change then
bubbles up to 'h a'. With 'repeatR (topdownR r)' the same result is reached by
starting another complete traversal from 'h'.
Only use 'topdownFixR' as a replacement for 'repeatR (topdownR r)' when 'r' is
local and context-stable: it should fire or fail based on the current node, and
the relevant parts of 'TransformContext' should not change when sibling
subtrees are rewritten. Rewrites that inspect let-bound context whose binding
terms may have changed, for example through 'whnfRW', still need an outer
repeat or a normal repeated top-down traversal.
-}
-- | Apply a transformation in a repeated top-down traversal.
--
-- Optimized for local, context-stable transformations. See Note [topdownFixR].
topdownFixR :: Rewrite m -> Rewrite m
topdownFixR r = go True
where
go tryParent ctx term = do
term1 <-
if tryParent
then repeatR r ctx term
else pure term
(term2, Monoid.getAny -> childChanged) <- Writer.listen (allR (go True) ctx term1)
if childChanged
then do
(term3, Monoid.getAny -> parentChanged) <- Writer.listen (repeatR r ctx term2)
if parentChanged
then go False ctx term3
else return term3
else return term2
{-# INLINE topdownFixR #-}
-- | Apply a transformation in a bottomup traversal
bottomupR :: Monad m => Transform m -> Transform m
bottomupR r = allR (bottomupR r) >-> r
infixr 5 !->
-- | Only apply the second transformation if the first one succeeds.
(!->) :: Rewrite m -> Rewrite m -> Rewrite m
(!->) = \r1 r2 c expr -> do
(expr',changed) <- Writer.listen $ r1 c expr
if Monoid.getAny changed
then r2 c expr'
else return expr'
{-# INLINE (!->) #-}
infixr 5 >-!
-- | Only apply the second transformation if the first one fails.
(>-!) :: Rewrite m -> Rewrite m -> Rewrite m
(>-!) = \r1 r2 c expr -> do
(expr',changed) <- Writer.listen $ r1 c expr
if Monoid.getAny changed
then return expr'
else r2 c expr'
{-# INLINE (>-!) #-}
-- | Keep applying a transformation until it fails.
repeatR :: Rewrite m -> Rewrite m
repeatR = let go r = r !-> repeatR r in go
{-# INLINE repeatR #-}