Plan 9 from Bell Labs’s /usr/web/sources/contrib/fernan/nhc98/src/compiler98/ByteCode/Graph.hs

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


-- | Converts the jump and label linear instruction list into a flow graph,
--   this aids later optimisation and memory phases.
--
--   It also includes the code for the graph monad which is used extensively
--   in later analysis processes.

module ByteCode.Graph(

-- * Bytecode graph conversion
bcGraph

-- * Graph state monad
, GState(..)
, GraphMonad
, gGetStart
, gSetStart
, gGetLabels
, gGetNode
, gSetNode
, gGetJumpers
, gSetJumpers
, gAddJumpers
, gRemoveJumpers
, gAlwaysReturns
, gReadX
, gWriteX
, gWriteX_

, module StateMonad
) where

import ByteCode.Type
import StateMonad
import Control.Monad.State
import qualified Data.Map as Map
import qualified Data.Set as Set
import Maybe(fromJust)

----------------------------------------------------------------------
-- graph monad
----------------------------------------------------------------------

-- | Generic graph monad state
data GState x = GState { gsStart :: GLabel,
                         gsGraph :: Graph,
                         gsJumpers :: Jumpers,
                         gsExtra :: x }

-- | A graph monad with a given extra state, returning a given value
type GraphMonad x a = State (GState x) a

----------------------------------------------------------------------

gGetStart :: GraphMonad x GLabel
gGetStart = readState gsStart

gSetStart :: GLabel -> GraphMonad x ()
gSetStart start = writeState_ $ \s -> s { gsStart = start }

gGetLabels :: GraphMonad x [GLabel]
gGetLabels = readState $ \s -> (map fst . Map.toList . gsGraph) s

gGetNode :: GLabel -> GraphMonad x GraphNode
gGetNode label = readState $ \s -> fromJust $ Map.lookup label (gsGraph s)

gSetNode :: GLabel -> GraphNode -> GraphMonad x ()
gSetNode label node = writeState_ $ \s -> s { gsGraph = Map.insert label node (gsGraph s) }

gGetJumpers :: GLabel -> GraphMonad x (Set.Set GLabel)
gGetJumpers label = readState $ \s -> maybe Set.empty id $ Map.lookup label (gsJumpers s)

gSetJumpers :: GLabel -> Set.Set GLabel -> GraphMonad x ()
gSetJumpers label jumps = writeState_ $ \s -> s { gsJumpers = Map.insert label jumps (gsJumpers s) }

gAddJumpers :: GLabel -> Set.Set GLabel -> GraphMonad x ()
gAddJumpers label jumps = do old <- gGetJumpers label
                             gSetJumpers label (old `Set.union` jumps)

gRemoveJumpers :: GLabel -> Set.Set GLabel -> GraphMonad x ()
gRemoveJumpers label jumps = do old <- gGetJumpers label
                                gSetJumpers label (old `Set.difference` jumps)

gReadX :: (x -> a) -> GraphMonad x a
gReadX f = readState $ \s -> f (gsExtra s)

gWriteX :: (x -> (x,a)) -> GraphMonad x a
gWriteX f = writeState $ \s -> let (x',a) = f (gsExtra s)
                               in (s { gsExtra = x' }, a)

gWriteX_ :: (x -> x) -> GraphMonad x ()
gWriteX_ f = gWriteX (\x -> (f x,()))

-- | Given a node, chase down it to see if it invariably ends up
--   at a return without doing anything substantial on the way
gAlwaysReturns :: GLabel -> GraphMonad x Bool
gAlwaysReturns label =
    do node <- gGetNode label
       case node of
           GLinear ins False next ->
               if allSlides ins then gAlwaysReturns next
                                else return False
           GReturn -> return True
           GDead   -> error $ "gAlwaysReturns: somehow reached dead code! "++show label
           _       -> return False
    where
    -- given a list of instructions returns whether all the instructions are
    -- slides, pops or need heaps. If so then they can be ignored before a return
    allSlides []                   = True
    allSlides ((NEED_HEAP n,_):is) = allSlides is
    allSlides ((SLIDE n,_):is)     = allSlides is
    allSlides ((POP n,_):is)       = allSlides is
    allSlides _                    = False

----------------------------------------------------------------------
-- graph builder specifics
----------------------------------------------------------------------

-- extra state for graph building
data Extra = Extra { sMapping :: Map.Map Label GLabel,
                     sLabels :: [GLabel] }

type Builder a = GraphMonad Extra a

----------------------------------------------------------------------
-- monadic helpers for building graphs
----------------------------------------------------------------------

-- bind a label to particular graph node
bind :: GLabel -> GraphNode -> Builder ()
bind label node = gSetNode label node

-- allocate a new unique label
newLabel :: Builder GLabel
newLabel = gWriteX $ \s -> let (l:ls) = sLabels s
                           in (s { sLabels = ls }, l)

-- record a label mapping from old labels to new labels
mapping :: Label -> GLabel -> Builder ()
mapping from to = gWriteX_ $ \s -> s { sMapping = Map.insert from to (sMapping s) }


-- records what a particular label is mapped to, if anything
mappedTo :: Label -> Builder (Maybe GLabel)
mappedTo from = gReadX $ \s -> Map.lookup from (sMapping s)

-- records a jump mapping from a label to a list of target labels. Note that this is actually stored
-- the other way round, i.e. record all the labels that jump to a particular label
jumpsTo :: GLabel -> [GLabel] -> Builder ()
jumpsTo from to = mapM_ (addJump from) to
    where
    addJump from to = gAddJumpers to (Set.singleton from)

----------------------------------------------------------------------
-- graph building
----------------------------------------------------------------------

-- | Turn linear bytecode into a graph representation.
bcGraph :: BCModule -> BCModule
bcGraph m = m { bcmDecls = map bcDecl $ bcmDecls m }

-- build the graph for a single declaration
bcDecl :: BCDecl -> BCDecl
bcDecl (Fun name pos arity args (CLinear code) consts isPrim stack numDict fl) =
    Fun name pos arity args graph consts isPrim stack numDict fl
    where
    st          = GState undefined Map.empty Map.empty (Extra Map.empty (map GLabel [0..]))
    (start,st') = runState (gBody code) st
    graph       = CGraph start (gsGraph st') (gsJumpers st')
bcDecl x                                                            = x

-- build the body of a function
gBody :: [UseIns] -> Builder GLabel
gBody code = do ret <- gCode code []
                oGraph ret
                return ret

-- build a graph out of a list of instructions, uses an accumulator to store
-- which nodes should go in the current block
gCode :: [UseIns] -> [UseIns] -> Builder GLabel
gCode [(RETURN,_)]     acc =
    do next <- newLabel
       accL <- gAcc acc next
       bind next GReturn
       return $ accL `orMaybe` next

gCode ((LABEL n,_):is) acc =
    do m <- mappedTo n
       next <- case m of
                 Just m  -> return m
                 Nothing -> do next <- gCode is []
                               mapping n next
                               return next
       accL <- gAcc acc next
       return $ accL `orMaybe` next

gCode ((EVAL,us):is) acc =
    do next <- gCode is []
       (Just accL) <- gAcc ((EVAL,us):acc) next
       return accL

gCode ((JUMP j,_):is) acc =
    do next <- gCodeAt j is []
       accL <- gAcc acc next
       return $ accL `orMaybe` next

gCode ((JUMP_FALSE j,_):is) acc =
    do true <- gCode is []
       false <- gCodeAt j is []
       next <- newLabel
       bind next (GIf true false)
       jumpsTo next [true,false]
       accL <- gAcc acc next
       return $ accL `orMaybe` next

gCode ((CASE int tas def,_):is) acc =
    do tas' <- mapM (gAlt is) tas
       next <- newLabel
       def' <- case def of
                   Just def -> do def' <- gCodeAt def is []
                                  jumpsTo next [def']
                                  return $ Just def'
                   Nothing  -> return Nothing
       bind next (GCase int tas' def')
       jumpsTo next (map snd tas')
       accL <- gAcc acc next
       return $ accL `orMaybe` next

gCode ((STOP,us):is) acc = gCode [(RETURN,us)] acc
gCode (i:is) acc = gCode is (i:acc)

-- does that same as gCode but skips instructions until it finds the given label
gCodeAt :: Label -> [UseIns] -> [UseIns] -> Builder GLabel
gCodeAt j is acc
    | null is'  = error $ "gCodeAt: cannot jump to label L_"++show j++" because it does not exist"
    | otherwise = gCode is' acc
    where
    is' = dropWhile (\k -> case k of
                               (LABEL k,_) -> j /= k
                               _           -> True) is

-- convert the accumulation buffer into a graph node if it's not empty
gAcc :: [UseIns] -> GLabel -> Builder (Maybe GLabel)
gAcc []  next  = return Nothing
gAcc acc next =
    do lab <- newLabel
       let isEval = case acc of
                        ((EVAL,_):_) -> True
                        _            -> False
       bind lab (GLinear (reverse acc) isEval next)
       jumpsTo lab [next]
       return (Just lab)

-- handle the alt of a case, simply a lifted gCodeAt
gAlt :: [UseIns] -> (Tag,Label) -> Builder (Tag,GLabel)
gAlt is (tag,j) = do next <- gCodeAt j is []
                     return (tag,next)

----------------------------------------------------------------------
-- helper functions
----------------------------------------------------------------------

-- return the first item if it's not Nothing, otherwise return the second
orMaybe :: Maybe a -> a -> a
orMaybe m d = maybe d id m

----------------------------------------------------------------------
-- graph optimisation functions
----------------------------------------------------------------------

oGraph :: GLabel -> Builder ()
oGraph label =
    do node <- gGetNode label
       case node of
           GLinear ins True next  -> oGraph next
           GLinear ins False next -> oLinear label node
           GCase int tas def      -> do mapM_ (\(t,as) -> oGraph as) tas
                                        case def of
                                            Just def -> oGraph def
                                            Nothing  -> return ()
           GIf true false         -> do oGraph true
                                        oGraph false
           GReturn                -> return ()

oLinear :: GLabel -> GraphNode -> Builder ()
oLinear label (GLinear ins False next) =
    do (extra,eval,next') <- oRemove next
       gSetNode label (GLinear (ins++extra) eval next')
       oGraph next'

oRemove :: GLabel -> Builder ([UseIns],Bool,GLabel)
oRemove label =
    do jumps <- gGetJumpers label
       if Set.size jumps /= 1 then
          return ([],False,label)
        else
          do node <- gGetNode label
             examine node
    where
    examine (GLinear ins eval next) =
        do more <- if not eval then
                     oRemove next
                    else
                     return ([],True,next)
           let (extra,eval',next') = more
           -- now we can remove this node
           oRemoveFromGraph label next'
           -- return the code
           return (ins ++ extra, eval',next')

    examine _                       =
        return ([],False,label)


oRemoveFromGraph :: GLabel -> GLabel -> Builder ()
oRemoveFromGraph node next =
    do parents <- gGetJumpers node
       gRemoveJumpers next (Set.singleton node)
       gAddJumpers next parents
       gSetNode node GDead
       gSetJumpers node Set.empty

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].