-- | Analyses variable for reusal.
-- So that we spend less memory and allocate lesser variables and audio vectors.
module Csound.Dynamic.Tfm.Liveness (
liveness
) where
import Prelude hiding (mapM, mapM_)
import Control.Monad.Trans.State.Strict
import Data.Traversable
import Data.Foldable
import Control.Monad.Trans.Class
import Control.Monad hiding (mapM, mapM_)
import Control.Monad.ST
import Data.Vector.Unboxed.Mutable qualified as UVector
import Csound.Dynamic.Tfm.InferTypes (Var (..))
import Csound.Dynamic.Types.Exp(Rate(..))
-- | Reuses variables. It analyses weather the vraibel is used further
-- in the code and if it's not used it tries to reuse it for the next assignments
liveness :: Traversable f => Int -> Dag f -> Dag f
liveness lastFreshId as = runST $ do
st <- initSt lastFreshId =<< analyse lastFreshId as
evalStateT (mapM substExp $ countLines $ as) st
type LineNumber = Int
countLines :: [a] -> [(LineNumber, a)]
countLines = zip [0 ..]
type Lhs = [Var]
type Rhs f = f Var
type Exp f = (Lhs, Rhs f)
type Dag f = [Exp f]
-----------------------------------------------
data IdList = IdList
[Int] -- fresh ids (always infinite list)
!Int -- the biggest used id
allocId :: IdList -> (Int, IdList)
allocId (IdList is lastId) =
case is of
hd : tl -> (hd, IdList tl (max hd lastId))
[] -> error "impossible: list of IDs is always infinite"
freeId :: Int -> IdList -> IdList
freeId n (IdList is lastId) = IdList (insertSorted n is) lastId1
where lastId1 = if (n == lastId) then (lastId - 1) else lastId
insertSorted :: Int -> [Int] -> [Int]
insertSorted n (a:as)
| n < a = n : a : as
| n == a = a : as
| otherwise = a : insertSorted n as
insertSorted n [] = [n]
initIdList :: IdList
initIdList = IdList [0..] 0
-----------------------------------------------
type StArr s = UVector.STVector s Int
type LivenessTable s = UVector.STVector s Int
type SubstTable s = StArr s
data Registers s = Registers
{ arRegisters :: !IdList
, krRegisters :: !IdList
, livenessTable :: !(LivenessTable s)
, substTable :: !(SubstTable s)
}
type Memory s a = StateT (Registers s) (ST s) a
onRegs :: Rate -> (IdList -> IdList) -> Memory s ()
onRegs rate f = modify' $ \rs ->
case rate of
Ar -> rs { arRegisters = f $ arRegisters rs }
Kr -> rs { krRegisters = f $ krRegisters rs }
_ -> rs
setArRegisters :: IdList -> Memory s ()
setArRegisters ids = modify' $ \s -> s { arRegisters = ids }
setKrRegisters :: IdList -> Memory s ()
setKrRegisters ids = modify' $ \s -> s { krRegisters = ids }
isAlive :: LineNumber -> Var -> Memory s Bool
isAlive lineNum v = do
tab <- fmap livenessTable get
lastUsage <- UVector.read tab (varId v)
pure $ lineNum < lastUsage
lookUpSubst :: Int -> Memory s Int
lookUpSubst i = do
tab <- fmap substTable get
lift $ UVector.read tab i
saveSubst :: Int -> Int -> Memory s ()
saveSubst from to = do
tab <- fmap substTable get
lift $ UVector.write tab from to
substLhs :: Var -> Memory s Var
substLhs = onlyForAK $ \v -> do
v1 <- alloc v
saveSubst (varId v) (varId v1)
return v1
substRhs :: LineNumber -> Var -> Memory s Var
substRhs lineNum = onlyForAK $ \v -> do
i1 <- lookUpSubst (varId v)
let v1 = Var (varType v) i1
b <- isAlive lineNum v
unless b $ free v1
return v1
alloc :: Var -> Memory s Var
alloc v =
case varType v of
Ar -> allocBy arRegisters setArRegisters
Kr -> allocBy krRegisters setKrRegisters
_ -> pure v
where
allocBy extract update = do
ids <- gets extract
let (name, newIds) = allocId ids
void $ update newIds
pure (Var (varType v) name)
free :: Var -> Memory s ()
free (Var rate name) = onRegs rate (freeId name)
--------------------------------------------------------------------------
analyse :: Traversable f => Int -> Dag f -> ST s (LivenessTable s)
analyse lastFreshId as = do
arr <- UVector.replicate lastFreshId 0
mapM_ (go arr) $ countLines as
return arr
where
go :: Traversable f => StArr s -> (LineNumber, Exp f) -> ST s ()
go arr (lineNum, (_, rhs)) = mapM_ (countVar arr lineNum) rhs
countVar :: StArr s -> LineNumber -> Var -> ST s ()
countVar arr lineNum v
| isAOrK v = UVector.write arr (varId v) lineNum
| otherwise = pure ()
onlyForAK :: Monad f => (Var -> f Var) -> Var -> f Var
onlyForAK go v
| isAOrK v = go v
| otherwise = pure v
-- we optimise for livenes only for Ar and Kr variables
isAOrK :: Var -> Bool
isAOrK v =
case varType v of
Ar -> True
Kr -> True
_ -> False
substExp :: Traversable f => (LineNumber, Exp f) -> Memory s (Exp f)
substExp (lineNum, (lhs, rhs)) = do
freshLhs <- traverse substLhs lhs
freshRhs <- traverse (substRhs lineNum) rhs
return (freshLhs, freshRhs)
initSt :: Int -> LivenessTable s -> ST s (Registers s)
initSt lastFreshId livenessTab = fmap (Registers initIdList initIdList livenessTab) (initSubstTable lastFreshId)
initSubstTable :: Int -> ST s (SubstTable s)
initSubstTable n = UVector.generate (n + 1) id