packages feed

linearscan-0.3.0.1: test/Tempest.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ConstraintKinds #-}

{-# OPTIONS_GHC -Wall -Werror #-}
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Tempest where

import           Compiler.Hoopl as Hoopl hiding ((<*>))
import           Control.Applicative
import           Control.Exception
import           Control.Lens
import           Control.Monad.Free
import           Control.Monad.State.Class
import           Control.Monad.Trans.Class
import qualified Control.Monad.Trans.Free as TF
import           Control.Monad.Trans.Free hiding (FreeF(..), Free)
import           Control.Monad.Trans.State (StateT, evalStateT, evalState)
import           Data.Foldable
import qualified Data.List
import qualified Data.Map as M
import           Data.Maybe (fromMaybe)
import           Data.Monoid
-- import           Debug.Trace
import           LinearScan
import           Test.Hspec

------------------------------------------------------------------------------
-- The input from the Tempest compiler has the following shape: 'Procedure a
-- IRVar', which means that instructions ultimately refer to either physical
-- registers, or virtual variables (by index).
--
-- The output from the register allocator should be as close to the input as
-- possible, with the difference that it has type 'Procedure a Reg', meaning
-- that only physical registers are referenced.
--
-- So the main allocation algorithm roughly has this type at present:
--
--     regAlloc :: Procedure a IRVar -> Procedure a Reg
------------------------------------------------------------------------------

data AtomicGroup = AtomicGroup deriving (Eq, Show)
type Name = String

newtype Linearity = Linearity { isLinear :: Bool }
  deriving (Eq, Show)

-- | Tests used for branching (correspond to branching instructions)
data Test
  -- | beq
  = Zero
  -- | bne
  | NonZero
  -- | bgt
  | Positive
  -- | blt
  | Negative
  deriving (Eq, Show)

data CConv
  = CConvC {
      ccArgs     :: [Reg],
      ccResults  :: [Reg],
      ccIsBrack  :: Bool
    }
  | InlineC
  deriving (Eq, Show)

data Constant = Constant deriving (Eq, Show)

type Src a      = a

-- | Type synonym for indicating destination operands
type Dst a      = a

-- | Type synonym for indicating success or true branch
type Success a  = a

-- | Type synonym for indicating failure or false branch
type Failure a  = a

-- | Type synonym for indicating an external name
type Imported a = a

type Reg = Int

data Instruction reg
  = Add          reg reg reg
  | Nop
  deriving (Eq, Show, Functor, Foldable, Traversable)

data IRInstr v e x where
  Label         :: Label -> IRInstr v C O
  Alloc         :: AtomicGroup -> Maybe (Src v) -> Dst v -> IRInstr v O O
  Reclaim       :: Src v -> IRInstr v O O
  Instr         :: Instruction v -> IRInstr v O O
  Call          :: CConv -> Instruction v -> IRInstr v O O
  LoadConst     :: Constant -> Dst v -> IRInstr v O O
  Move          :: Src v -> Dst v -> IRInstr v O O
  Copy          :: Src v -> Dst v -> IRInstr v O O
  Save          :: Linearity -> Src v -> Dst Int -> IRInstr v O O
  Restore       :: Linearity -> Src Int -> Dst v -> IRInstr v O O
  SaveOffset    :: Linearity -> Int -> Src v -> Dst Int -> IRInstr v O O
  RestoreOffset :: Linearity -> Int -> Src Int -> Dst v -> IRInstr v O O
  Jump          :: Label -> IRInstr v O C
  Branch        :: Test -> v -> Success Label -> Failure Label
                -> IRInstr v O C
  Stwb          :: Linearity -> Src v -> Dst v
                -> Success Label -> Failure Label -> IRInstr v O C
  Strb          :: Src v -> Dst v -> Success Label -> Failure Label
                -> IRInstr v O C
  ReturnInstr   :: [Reg] -> Instruction v -> IRInstr v O C

deriving instance Eq v => Eq (IRInstr v e x)

instance Show v => Show (IRInstr v e x) where
  show (Label l)        = show l ++ ":"
  show (Alloc g x1 x2)  = "\t@alloc " ++ show g ++
                          (case x1 of Just v -> " " ++ show v ; _ -> " _")
                          ++ " " ++ show x2
  show (Reclaim v)      = "\t@reclaim " ++ show v
  show (Instr i)        = "\t" ++ show i
  show (Call c i)       = "\t@call " ++ show c ++ " " ++ show i
  show (LoadConst c v)  = "\t@lc " ++ show v ++ " " ++ show c
  show (Move x1 x2)     = "\t@mvrr " ++ show x1 ++ " " ++ show x2
  show (Copy x1 x2)     = "\t@cprr " ++ show x1 ++ " " ++ show x2
  show (Save (Linearity l) src dst)
                        = "\t@save " ++ show l ++ " " ++ show src ++ " " ++ show dst
  show (Restore (Linearity l) src dst)
                        = "\t@restore " ++ show l ++ " " ++ show src ++ " " ++ show dst
  show (SaveOffset (Linearity l) off src dst)
                        = unwords ["\t@saveoff", show l, show off, show src, show dst]
  show (RestoreOffset (Linearity l) off src dst)
                        = unwords ["\t@restoreoff", show l, show off, show src, show dst]
  show (Jump l)         = "\t@jmp " ++ show l
  show (Branch tst v t f)
                        = "\t@b" ++ show tst ++ " " ++ show v
                            ++ " " ++ show t
                            ++ "; @jmp " ++ show f
  show (Stwb lin x1 x2 t f)
                        = (if isLinear lin then "\t@stwlb " else "\t@stwb ")
                            ++ show x1 ++ " " ++ show x2
                            ++ " " ++ show f ++ "; @jmp " ++ show t
  show (Strb x1 x2 t f) = "\t@strb " ++ show x1 ++ " " ++ show x2
                            ++ " " ++ show f ++ "; @jmp " ++ show t
  show (ReturnInstr liveRegs i)   = "\t@return " ++ show liveRegs ++ " " ++ show i

data Node a v e x = Node
  { _nodeIRInstr :: IRInstr v e x
  , _nodeMeta    :: a
  } deriving Eq

instance Show v => Show (Node a v e x) where
    show (Node i _) = show i

instance NonLocal (Node a v) where
  entryLabel (Node (Label l)         _) = l
  successors (Node (Jump l)          _) = [l]
  successors (Node (Branch _ _ t f)  _) = [t, f]
  successors (Node (Stwb _ _ _ s f)  _) = [s, f]
  successors (Node (Strb _ _ s f)    _) = [s, f]
  successors (Node (ReturnInstr _ _) _) = []

data AtomKind = Atom deriving (Eq, Show)
data Var = Var deriving (Eq, Show)

data IRVar' = PhysicalIV !PhysReg
            | VirtualIV !Int !AtomKind
            deriving Eq

instance Show IRVar' where
    show (PhysicalIV r)  = "r" ++ show r
    show (VirtualIV n _) = "v" ++ show n

-- | Virtual IR variable together with an optional AST variable
data IRVar =
  IRVar
  { _ivVar :: !IRVar' -- ^ The virtual or physical register
  , _ivSrc :: !(Maybe Var) -- ^ An optional corresponding AST variable for
                       -- informational purposes.
  }
  deriving Eq

instance Show IRVar where
    show (IRVar x _) = show x

type Engine m = (UniqueMonad m, MonadState Labels m)

instance UniqueMonad (StateT Labels SimpleUniqueMonad) where
    freshUnique = lift freshUnique

asmTest :: (Engine m, m ~ StateT Labels SimpleUniqueMonad)
        => Int -> Program IRVar m () -> Program Reg m ()
        -> Expectation
asmTest regs (compile -> (prog, entry)) (compile -> (result, _)) =
    go $ M.fromList $ zip (Prelude.map entryLabel blocks) [(1 :: Int)..]
  where
    GMany NothingO body NothingO = prog
    blocks = postorder_dfs_from body entry

    go blockIds =
        case evalState
                 (allocate regs (blockInfo getBlockId) opInfo blocks)
                 (newSpillStack 0) of
            Left e -> error $ "Allocation failed: " ++ e
            Right blks -> do
                let graph' = newGraph blks
                catch
                    (showGraph show graph' `shouldBe` showGraph show result)
                    (\e -> do
                          putStrLn "---- Expecting ----"
                          putStr $ showGraph show result
                          putStrLn "---- Compiled  ----"
                          putStr $ showGraph show graph'
                          putStrLn "-------------------"
                          throwIO (e :: SomeException))
      where
        newBody = Data.Foldable.foldl' (flip addBlock) emptyBody
        newGraph xs = GMany NothingO (newBody xs) NothingO

        getBlockId :: Hoopl.Label -> Int
        getBlockId lbl =
            fromMaybe (error "The impossible happened")
                      (M.lookup lbl blockIds)

variables :: Traversal (IRInstr v1 e x) (IRInstr v2 e x) v1 v2
variables f = go
  where
    go (Alloc ag msrc dst)           = Alloc ag <$> traverse f msrc <*> f dst
    go (Reclaim src)                 = Reclaim <$> f src
    go (Instr i)                     = Instr <$> traverse f i
    go (LoadConst c dst)             = LoadConst c <$> f dst
    go (Move src dst)                = Move <$> f src <*> f dst
    go (Copy src dst)                = Copy <$> f src <*> f dst
    go (Save lin src x)              = Save lin <$> f src <*> pure x
    go (Restore x1 x2 dst)           = Restore x1 x2 <$> f dst
    go (SaveOffset lin off src x)    = SaveOffset lin off <$> f src <*> pure x
    go (RestoreOffset lin off x dst) = RestoreOffset lin off x <$> f dst
    go (Branch x1 cond x2 x3)        = Branch x1 <$> f cond
                                                 <*> pure x2 <*> pure x3
    go (Stwb x1 src dst x2 x3)       = Stwb x1 <$> f src <*> f dst
                                               <*> pure x2 <*> pure x3
    go (Strb src dst x2 x3)          = Strb <$> f src <*> f dst
                                            <*> pure x2 <*> pure x3
    go (Call cc i)                   = Call cc <$> traverse f i
    go (ReturnInstr liveInRegs i)    = ReturnInstr liveInRegs <$> traverse f i
    go (Label x)                     = pure $ Label x
    go (Jump x)                      = pure $ Jump x

metadata :: Lens (Node a1 v e x) (Node a2 v e x) a1 a2
metadata f (Node instr meta) = Node instr <$> f meta

irinstr :: Traversal (Node a v1 e x) (Node a v2 e x)
                  (IRInstr v1 e x) (IRInstr v2 e x)
irinstr f (Node instr meta) = Node <$> f instr <*> pure meta

data NodeV a v = NodeCO { getNodeCO :: Node a v C O }
               | NodeOO { getNodeOO :: Node a v O O }
               | NodeOC { getNodeOC :: Node a v O C }

instance Functor (NodeV v) where
    fmap f (NodeCO n) = NodeCO (over (irinstr.variables) f n)
    fmap f (NodeOO n) = NodeOO (over (irinstr.variables) f n)
    fmap f (NodeOC n) = NodeOC (over (irinstr.variables) f n)

blockInfo :: (Hoopl.Label -> Int)
          -> BlockInfo (Block (Node a IRVar) C C)
                      (Block (Node a Reg) C C)
                      (NodeV a IRVar)
                      (NodeV a Reg)
blockInfo getBlockId = BlockInfo
    { blockId = getBlockId . entryLabel

    , blockSuccessors = Prelude.map getBlockId . successors

    , blockOps = \(BlockCC a b z) ->
        ([NodeCO a], Prelude.map NodeOO (blockToList b), [NodeOC z])

    , setBlockOps = \_ [a] b [z] ->
        BlockCC
            (getNodeCO a)
            (blockFromList (Prelude.map getNodeOO b))
            (getNodeOC z)
    }

data StackInfo = StackInfo
    { stackPtr   :: Int
    , stackSlots :: M.Map (Maybe Int) Int
    }
    deriving (Eq, Show)

newSpillStack :: Int -> StackInfo
newSpillStack offset = StackInfo
    { stackPtr   = offset
    , stackSlots = mempty
    }

opInfo :: OpInfo StackInfo (NodeV a IRVar) (NodeV a Reg)
opInfo = OpInfo
    { opKind = \n -> case n of
           NodeOO (Node i _) -> case i of
               Call {} -> IsCall
               -- jww (2015-01-18): Identification of loop boundaries allows
               -- the allocator to perform a block ordering optimization to
               -- avoid excessive saves and restores, but it is optional.
               -- ?       -> LoopBegin
               -- ?       -> LoopEnd
               _ -> IsNormal
           NodeOC (Node i _) -> case i of
               Jump {}   -> IsBranch
               Branch {} -> IsBranch
               Strb {}   -> IsBranch
               Stwb {}   -> IsBranch
               _ -> IsNormal
           _ -> IsNormal

    , opRefs = \n -> let f = getReferences in case n of
           NodeCO o -> f o
           NodeOO o -> f o
           NodeOC o -> f o

    , moveOp = \sr dr -> do
        let mv = Move sr dr
        return [NodeOO (Node mv (error "no move meta"))]

    , swapOp = \sr dr ->
        liftA2 (++) (mkRestoreOp Nothing dr)
                    (mkSaveOp sr Nothing)

    , saveOp = mkSaveOp
    , restoreOp = mkRestoreOp

      -- Apply allocations, which changes IRVar's into Reg's.
    , applyAllocs = \node m -> [fmap (setRegister m) node]
    }
  where
    go :: Instruction IRVar -> [VarInfo]
    go Nop = mempty
    go (Add s1 s2 d1) =
        mkv Input s1 <> mkv Input s2 <> mkv Output d1

    mkv :: VarKind -> IRVar -> [VarInfo]
    mkv k (IRVar (PhysicalIV n) _)  = [vinfo k (Left n)]
    mkv k (IRVar (VirtualIV n _) _) = [vinfo k (Right n)]

    vinfo k en = VarInfo
        { varId   = en
        , varKind = k
          -- If there are variables which can be used directly from
          -- memory, then this can be False, which relaxes some
          -- requirements.
        , regRequired = True
        }

    getReferences :: Node a IRVar e x -> [VarInfo]
    getReferences (Node (Label _) _)         = mempty
    getReferences (Node (Instr i) _)         = go i
    getReferences (Node (Jump _) _)          = mempty
    getReferences (Node (Branch _ v _ _) _)  = mkv Input v
    getReferences (Node (ReturnInstr _ i) _) = go i
    getReferences n = error $ "getReferences: unhandled node: " ++ show n

    setRegister :: [(Int, PhysReg)] -> IRVar -> Reg
    setRegister _ (IRVar (PhysicalIV r) _)  = r
    setRegister m (IRVar (VirtualIV n _) _) =
        fromMaybe (error $ "Allocation failed for variable " ++ show n)
                  (Data.List.lookup n m)

mkSaveOp r vid = do
    stack <- get
    off' <- case M.lookup vid (stackSlots stack) of
        Just off -> return off
        Nothing -> do
            let off = stackPtr stack
            put StackInfo
                 { stackPtr   = off + 8
                 , stackSlots =
                     M.insert vid off (stackSlots stack)
                 }
            return off
    let sv = Save (Linearity False) r off'
    return [NodeOO (Node sv (error "no save meta"))]

mkRestoreOp vid r = do
    stack <- get
    let off = fromMaybe (-1) (M.lookup vid (stackSlots stack))
        rs  = Restore (Linearity False) off r
    return [NodeOO (Node rs (error "no restore meta"))]

var :: Int -> IRVar
var i = IRVar { _ivVar = VirtualIV i Atom
              , _ivSrc = Nothing
              }

fixed :: Int -> IRVar
fixed i = IRVar { _ivVar = PhysicalIV i
                , _ivSrc = Nothing
                }

reg :: PhysReg -> PhysReg
reg r = r

v0  = var 0
v1  = var 1
v2  = var 2
v3  = var 3
v4  = var 4
v5  = var 5
v6  = var 6
v7  = var 7
v8  = var 8
v9  = var 9
v10 = var 10
v11 = var 11
v12 = var 12
v13 = var 13
v14 = var 14
v15 = var 15
v16 = var 16
v17 = var 17
v18 = var 18
v19 = var 19
v20 = var 20
v21 = var 21
v22 = var 22
v23 = var 23
v24 = var 24
v25 = var 25
v26 = var 26
v27 = var 27
v28 = var 28
v29 = var 29
v30 = var 30
v31 = var 31
v32 = var 32
v33 = var 33
v34 = var 34
v35 = var 35

r0  = reg 0
r1  = reg 1
r2  = reg 2
r3  = reg 3
r4  = reg 4
r5  = reg 5
r6  = reg 6
r7  = reg 7
r8  = reg 8
r9  = reg 9
r10 = reg 10
r11 = reg 11
r12 = reg 12
r13 = reg 13
r14 = reg 14
r15 = reg 15
r16 = reg 16
r17 = reg 17
r18 = reg 18
r19 = reg 19
r20 = reg 20
r21 = reg 21
r22 = reg 22
r23 = reg 23
r24 = reg 24
r25 = reg 25
r26 = reg 26
r27 = reg 27
r28 = reg 28
r29 = reg 29
r30 = reg 30
r31 = reg 31
r32 = reg 32
r33 = reg 33
r34 = reg 34
r35 = reg 35

type BodyF v = Free ((,) (Node () v O O)) ()

nodesToList :: BodyF v -> [Node () v O O]
nodesToList (Pure ()) = []
nodesToList (Free (Node n meta, xs)) = Node n meta : nodesToList xs

data ProgramF m v
    = FreeLabel
      { labelEntry :: Label
      , labelBody  :: BodyF v
      , labelClose :: m (Node () v O C)
      }

type Program v m a = FreeT ((,) (ProgramF m v)) m a

type Labels = M.Map String Label

getLabel :: Engine m => String -> m Label
getLabel str = do
    l <- use (at str)
    case l of
        Just lbl -> return lbl
        Nothing -> do
            lbl <- freshLabel
            at str .= Just lbl
            return lbl

label :: Engine m => String -> BodyF v -> m (Node () v O C) -> Program v m ()
label str body close = do
    lbl <- lift $ getLabel str
    liftF (FreeLabel lbl body close, ())

compile :: (Engine m, m ~ StateT Labels SimpleUniqueMonad, NonLocal (Node () v))
        => Program v m () -> (Graph (Node () v) C C, Hoopl.Label)
compile prog = runSimpleUniqueMonad $
    flip evalStateT (mempty :: M.Map String Label) $ do
        body  <- go prog
        entry <- use (at "entry")
        case entry of
            Nothing -> error "Missing 'entry' label"
            Just lbl -> return (bodyGraph body, lbl)
  where
    go m = do
        p <- runFreeT m
        case p of
            TF.Pure ()        -> return emptyBody
            TF.Free (blk, xs) -> addBlock <$> comp blk <*> go xs

    comp (FreeLabel lbl body close) = do
        close' <- close
        return $ BlockCC (Node (Label lbl) ())
                         (blockFromList (nodesToList body)) close'

add :: v -> v -> v -> BodyF v
add x0 x1 x2 = Free (Node (Instr (Add x0 x1 x2)) (), Pure ())

move :: v -> v -> BodyF v
move x0 x1 = Free (Node (Move x0 x1) (), Pure ())

return_ :: Monad m => m (Node () v O C)
return_ = return $ Node (ReturnInstr [] Nop) ()

branch :: Engine m => Test -> v -> String -> String -> m (Node () v O C)
branch tst v good bad = do
    lblg <- getLabel good
    lblb <- getLabel bad
    return $ Node (Branch tst v lblg lblb) ()

jump :: Engine m => String -> m (Node () v O C)
jump dest = do
    lbl <- getLabel dest
    return $ Node (Jump lbl) ()

save :: PhysReg -> Dst Reg -> BodyF Reg
save r dst = Free (Node (Save (Linearity False) r dst) (), Pure ())

restore :: Src Reg -> PhysReg -> BodyF Reg
restore src r = Free (Node (Restore (Linearity False) src r) (), Pure ())