{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Hoopl.Graph ( Body , Graph , Graph'(..) , NonLocal(..) , addBlock , bodyList , emptyBody , labelsDefined , mapGraph , mapGraphBlocks , revPostorderFrom ) where import GhcPrelude import Util import Hoopl.Label import Hoopl.Block import Hoopl.Collections -- | A (possibly empty) collection of closed/closed blocks type Body n = LabelMap (Block n C C) -- | @Body@ abstracted over @block@ type Body' block (n :: * -> * -> *) = LabelMap (block n C C) ------------------------------- -- | Gives access to the anchor points for -- nonlocal edges as well as the edges themselves class NonLocal thing where entryLabel :: thing C x -> Label -- ^ The label of a first node or block successors :: thing e C -> [Label] -- ^ Gives control-flow successors instance NonLocal n => NonLocal (Block n) where entryLabel (BlockCO f _) = entryLabel f entryLabel (BlockCC f _ _) = entryLabel f successors (BlockOC _ n) = successors n successors (BlockCC _ _ n) = successors n emptyBody :: Body' block n emptyBody = mapEmpty bodyList :: Body' block n -> [(Label,block n C C)] bodyList body = mapToList body addBlock :: (NonLocal block, HasDebugCallStack) => block C C -> LabelMap (block C C) -> LabelMap (block C C) addBlock block body = mapAlter add lbl body where lbl = entryLabel block add Nothing = Just block add _ = error $ "duplicate label " ++ show lbl ++ " in graph" -- --------------------------------------------------------------------------- -- Graph -- | A control-flow graph, which may take any of four shapes (O/O, -- O/C, C/O, C/C). A graph open at the entry has a single, -- distinguished, anonymous entry point; if a graph is closed at the -- entry, its entry point(s) are supplied by a context. type Graph = Graph' Block -- | @Graph'@ is abstracted over the block type, so that we can build -- graphs of annotated blocks for example (Compiler.Hoopl.Dataflow -- needs this). data Graph' block (n :: * -> * -> *) e x where GNil :: Graph' block n O O GUnit :: block n O O -> Graph' block n O O GMany :: MaybeO e (block n O C) -> Body' block n -> MaybeO x (block n C O) -> Graph' block n e x -- ----------------------------------------------------------------------------- -- Mapping over graphs -- | Maps over all nodes in a graph. mapGraph :: (forall e x. n e x -> n' e x) -> Graph n e x -> Graph n' e x mapGraph f = mapGraphBlocks (mapBlock f) -- | Function 'mapGraphBlocks' enables a change of representation of blocks, -- nodes, or both. It lifts a polymorphic block transform into a polymorphic -- graph transform. When the block representation stabilizes, a similar -- function should be provided for blocks. mapGraphBlocks :: forall block n block' n' e x . (forall e x . block n e x -> block' n' e x) -> (Graph' block n e x -> Graph' block' n' e x) mapGraphBlocks f = map where map :: Graph' block n e x -> Graph' block' n' e x map GNil = GNil map (GUnit b) = GUnit (f b) map (GMany e b x) = GMany (fmap f e) (mapMap f b) (fmap f x) -- ----------------------------------------------------------------------------- -- Extracting Labels from graphs labelsDefined :: forall block n e x . NonLocal (block n) => Graph' block n e x -> LabelSet labelsDefined GNil = setEmpty labelsDefined (GUnit{}) = setEmpty labelsDefined (GMany _ body x) = mapFoldlWithKey addEntry (exitLabel x) body where addEntry :: forall a. LabelSet -> ElemOf LabelSet -> a -> LabelSet addEntry labels label _ = setInsert label labels exitLabel :: MaybeO x (block n C O) -> LabelSet exitLabel NothingO = setEmpty exitLabel (JustO b) = setSingleton (entryLabel b) ---------------------------------------------------------------- -- | Returns a list of blocks reachable from the provided Labels in the reverse -- postorder. -- -- This is the most important traversal over this data structure. It drops -- unreachable code and puts blocks in an order that is good for solving forward -- dataflow problems quickly. The reverse order is good for solving backward -- dataflow problems quickly. The forward order is also reasonably good for -- emitting instructions, except that it will not usually exploit Forrest -- Baskett's trick of eliminating the unconditional branch from a loop. For -- that you would need a more serious analysis, probably based on dominators, to -- identify loop headers. -- -- For forward analyses we want reverse postorder visitation, consider: -- @ -- A -> [B,C] -- B -> D -- C -> D -- @ -- Postorder: [D, C, B, A] (or [D, B, C, A]) -- Reverse postorder: [A, B, C, D] (or [A, C, B, D]) -- This matters for, e.g., forward analysis, because we want to analyze *both* -- B and C before we analyze D. revPostorderFrom :: forall block. (NonLocal block) => LabelMap (block C C) -> Label -> [block C C] revPostorderFrom graph start = go start_worklist setEmpty [] where start_worklist = lookup_for_descend start Nil -- To compute the postorder we need to "visit" a block (mark as done) -- *after* visiting all its successors. So we need to know whether we -- already processed all successors of each block (and @NonLocal@ allows -- arbitrary many successors). So we use an explicit stack with an extra bit -- of information: -- * @ConsTodo@ means to explore the block if it wasn't visited before -- * @ConsMark@ means that all successors were already done and we can add -- the block to the result. -- -- NOTE: We add blocks to the result list in postorder, but we *prepend* -- them (i.e., we use @(:)@), which means that the final list is in reverse -- postorder. go :: DfsStack (block C C) -> LabelSet -> [block C C] -> [block C C] go Nil !_ !result = result go (ConsMark block rest) !wip_or_done !result = go rest wip_or_done (block : result) go (ConsTodo block rest) !wip_or_done !result | entryLabel block `setMember` wip_or_done = go rest wip_or_done result | otherwise = let new_worklist = foldr lookup_for_descend (ConsMark block rest) (successors block) in go new_worklist (setInsert (entryLabel block) wip_or_done) result lookup_for_descend :: Label -> DfsStack (block C C) -> DfsStack (block C C) lookup_for_descend label wl | Just b <- mapLookup label graph = ConsTodo b wl | otherwise = error $ "Label that doesn't have a block?! " ++ show label data DfsStack a = ConsTodo a (DfsStack a) | ConsMark a (DfsStack a) | Nil