-- | 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