| 1 | {-# LANGUAGE DataKinds #-} |
| 2 | {-# LANGUAGE GADTs #-} |
| 3 | {-# LANGUAGE TypeFamilies #-} |
| 4 | |
| 5 | -- |
| 6 | -- Copyright (c) 2010, João Dias, Simon Marlow, Simon Peyton Jones, |
| 7 | -- and Norman Ramsey |
| 8 | -- |
| 9 | -- Modifications copyright (c) The University of Glasgow 2012 |
| 10 | -- |
| 11 | -- This module is a specialised and optimised version of |
| 12 | -- Compiler.Hoopl.Dataflow in the hoopl package. In particular it is |
| 13 | -- specialised to the UniqDSM monad. |
| 14 | -- |
| 15 | |
| 16 | module GHC.Cmm.Dataflow |
| 17 | ( C, O, Block |
| 18 | , lastNode, entryLabel |
| 19 | , foldNodesBwdOO |
| 20 | , foldRewriteNodesBwdOO |
| 21 | , DataflowLattice(..), OldFact(..), NewFact(..), JoinedFact(..) |
| 22 | , TransferFun, RewriteFun |
| 23 | , Fact, FactBase |
| 24 | , getFact, mkFactBase |
| 25 | , analyzeCmmFwd, analyzeCmmBwd |
| 26 | , rewriteCmmBwd |
| 27 | , changedIf |
| 28 | , joinOutFacts |
| 29 | , joinFacts |
| 30 | ) |
| 31 | where |
| 32 | |
| 33 | import GHC.Prelude |
| 34 | |
| 35 | import GHC.Cmm |
| 36 | import GHC.Types.Unique.DSM |
| 37 | |
| 38 | import Data.Array |
| 39 | import Data.Maybe |
| 40 | import Data.IntSet (IntSet) |
| 41 | import qualified Data.IntSet as IntSet |
| 42 | import Data.Kind (Type) |
| 43 | |
| 44 | import GHC.Cmm.Dataflow.Block |
| 45 | import GHC.Cmm.Dataflow.Graph |
| 46 | import GHC.Cmm.Dataflow.Label |
| 47 | |
| 48 | type family Fact (x :: Extensibility) f :: Type |
| 49 | type instance Fact C f = FactBase f |
| 50 | type instance Fact O f = f |
| 51 | |
| 52 | newtype OldFact a = OldFact a |
| 53 | |
| 54 | newtype NewFact a = NewFact a |
| 55 | |
| 56 | -- | The result of joining OldFact and NewFact. |
| 57 | data JoinedFact a |
| 58 | = Changed !a -- ^ Result is different than OldFact. |
| 59 | | NotChanged !a -- ^ Result is the same as OldFact. |
| 60 | |
| 61 | getJoined :: JoinedFact a -> a |
| 62 | getJoined (Changed a) = a |
| 63 | getJoined (NotChanged a) = a |
| 64 | |
| 65 | changedIf :: Bool -> a -> JoinedFact a |
| 66 | changedIf True = Changed |
| 67 | changedIf False = NotChanged |
| 68 | |
| 69 | type JoinFun a = OldFact a -> NewFact a -> JoinedFact a |
| 70 | |
| 71 | data DataflowLattice a = DataflowLattice |
| 72 | { fact_bot :: a |
| 73 | , fact_join :: JoinFun a |
| 74 | } |
| 75 | |
| 76 | data Direction = Fwd | Bwd |
| 77 | |
| 78 | type TransferFun f = CmmBlock -> FactBase f -> FactBase f |
| 79 | |
| 80 | -- | `TransferFun` abstracted over `n` (the node type) |
| 81 | type TransferFun' (n :: Extensibility -> Extensibility -> Type) f = |
| 82 | Block n C C -> FactBase f -> FactBase f |
| 83 | |
| 84 | |
| 85 | -- | Function for rewriting and analysis combined. To be used with |
| 86 | -- @rewriteCmm@. |
| 87 | -- |
| 88 | -- Currently set to work with @UniqDSM@ monad, but we could probably abstract |
| 89 | -- that away (if we do that, we might want to specialize the fixpoint algorithms |
| 90 | -- to the particular monads through SPECIALIZE). |
| 91 | type RewriteFun f = CmmBlock -> FactBase f -> UniqDSM (CmmBlock, FactBase f) |
| 92 | |
| 93 | -- | `RewriteFun` abstracted over `n` (the node type) |
| 94 | type RewriteFun' (n :: Extensibility -> Extensibility -> Type) f = |
| 95 | Block n C C -> FactBase f -> UniqDSM (Block n C C, FactBase f) |
| 96 | |
| 97 | analyzeCmmBwd, analyzeCmmFwd |
| 98 | :: (NonLocal node) |
| 99 | => DataflowLattice f |
| 100 | -> TransferFun' node f |
| 101 | -> GenCmmGraph node |
| 102 | -> FactBase f |
| 103 | -> FactBase f |
| 104 | analyzeCmmBwd = analyzeCmm Bwd |
| 105 | analyzeCmmFwd = analyzeCmm Fwd |
| 106 | |
| 107 | analyzeCmm |
| 108 | :: (NonLocal node) |
| 109 | => Direction |
| 110 | -> DataflowLattice f |
| 111 | -> TransferFun' node f |
| 112 | -> GenCmmGraph node |
| 113 | -> FactBase f |
| 114 | -> FactBase f |
| 115 | analyzeCmm dir lattice transfer cmmGraph initFact = |
| 116 | {-# SCC analyzeCmm #-} |
| 117 | let entry = g_entry cmmGraph |
| 118 | hooplGraph = g_graph cmmGraph |
| 119 | blockMap = |
| 120 | case hooplGraph of |
| 121 | GMany NothingO bm NothingO -> bm |
| 122 | in fixpointAnalysis dir lattice transfer entry blockMap initFact |
| 123 | |
| 124 | -- Fixpoint algorithm. |
| 125 | fixpointAnalysis |
| 126 | :: forall f node. |
| 127 | (NonLocal node) |
| 128 | => Direction |
| 129 | -> DataflowLattice f |
| 130 | -> TransferFun' node f |
| 131 | -> Label |
| 132 | -> LabelMap (Block node C C) |
| 133 | -> FactBase f |
| 134 | -> FactBase f |
| 135 | fixpointAnalysis direction lattice do_block entry blockmap = loop start |
| 136 | where |
| 137 | -- Sorting the blocks helps to minimize the number of times we need to |
| 138 | -- process blocks. For instance, for forward analysis we want to look at |
| 139 | -- blocks in reverse postorder. Also, see comments for sortBlocks. |
| 140 | blocks = sortBlocks direction entry blockmap |
| 141 | num_blocks = length blocks |
| 142 | block_arr = {-# SCC "block_arr" #-} listArray (0, num_blocks - 1) blocks |
| 143 | start = {-# SCC "start" #-} IntSet.fromDistinctAscList |
| 144 | [0 .. num_blocks - 1] |
| 145 | dep_blocks = {-# SCC "dep_blocks" #-} mkDepBlocks direction blocks |
| 146 | join = fact_join lattice |
| 147 | |
| 148 | loop |
| 149 | :: IntHeap -- Worklist, i.e., blocks to process |
| 150 | -> FactBase f -- Current result (increases monotonically) |
| 151 | -> FactBase f |
| 152 | loop todo !fbase1 | Just (index, todo1) <- IntSet.minView todo = |
| 153 | let block = block_arr ! index |
| 154 | out_facts = {-# SCC "do_block" #-} do_block block fbase1 |
| 155 | -- For each of the outgoing edges, we join it with the current |
| 156 | -- information in fbase1 and (if something changed) we update it |
| 157 | -- and add the affected blocks to the worklist. |
| 158 | (todo2, fbase2) = {-# SCC "mapFoldWithKey" #-} |
| 159 | mapFoldlWithKey |
| 160 | (updateFact join dep_blocks) (todo1, fbase1) out_facts |
| 161 | in loop todo2 fbase2 |
| 162 | loop _ !fbase1 = fbase1 |
| 163 | |
| 164 | rewriteCmmBwd |
| 165 | :: (NonLocal node) |
| 166 | => DataflowLattice f |
| 167 | -> RewriteFun' node f |
| 168 | -> GenCmmGraph node |
| 169 | -> FactBase f |
| 170 | -> UniqDSM (GenCmmGraph node, FactBase f) |
| 171 | rewriteCmmBwd = rewriteCmm Bwd |
| 172 | |
| 173 | rewriteCmm |
| 174 | :: (NonLocal node) |
| 175 | => Direction |
| 176 | -> DataflowLattice f |
| 177 | -> RewriteFun' node f |
| 178 | -> GenCmmGraph node |
| 179 | -> FactBase f |
| 180 | -> UniqDSM (GenCmmGraph node, FactBase f) |
| 181 | rewriteCmm dir lattice rwFun cmmGraph initFact = {-# SCC rewriteCmm #-} do |
| 182 | let entry = g_entry cmmGraph |
| 183 | hooplGraph = g_graph cmmGraph |
| 184 | blockMap1 = |
| 185 | case hooplGraph of |
| 186 | GMany NothingO bm NothingO -> bm |
| 187 | (blockMap2, facts) <- |
| 188 | fixpointRewrite dir lattice rwFun entry blockMap1 initFact |
| 189 | return (cmmGraph {g_graph = GMany NothingO blockMap2 NothingO}, facts) |
| 190 | |
| 191 | fixpointRewrite |
| 192 | :: forall f node. |
| 193 | NonLocal node |
| 194 | => Direction |
| 195 | -> DataflowLattice f |
| 196 | -> RewriteFun' node f |
| 197 | -> Label |
| 198 | -> LabelMap (Block node C C) |
| 199 | -> FactBase f |
| 200 | -> UniqDSM (LabelMap (Block node C C), FactBase f) |
| 201 | fixpointRewrite dir lattice do_block entry blockmap = loop start blockmap |
| 202 | where |
| 203 | -- Sorting the blocks helps to minimize the number of times we need to |
| 204 | -- process blocks. For instance, for forward analysis we want to look at |
| 205 | -- blocks in reverse postorder. Also, see comments for sortBlocks. |
| 206 | blocks = sortBlocks dir entry blockmap |
| 207 | num_blocks = length blocks |
| 208 | block_arr = {-# SCC "block_arr_rewrite" #-} |
| 209 | listArray (0, num_blocks - 1) blocks |
| 210 | start = {-# SCC "start_rewrite" #-} |
| 211 | IntSet.fromDistinctAscList [0 .. num_blocks - 1] |
| 212 | dep_blocks = {-# SCC "dep_blocks_rewrite" #-} mkDepBlocks dir blocks |
| 213 | join = fact_join lattice |
| 214 | |
| 215 | loop |
| 216 | :: IntHeap -- Worklist, i.e., blocks to process |
| 217 | -> LabelMap (Block node C C) -- Rewritten blocks. |
| 218 | -> FactBase f -- Current facts. |
| 219 | -> UniqDSM (LabelMap (Block node C C), FactBase f) |
| 220 | loop todo !blocks1 !fbase1 |
| 221 | | Just (index, todo1) <- IntSet.minView todo = do |
| 222 | -- Note that we use the *original* block here. This is important. |
| 223 | -- We're optimistically rewriting blocks even before reaching the fixed |
| 224 | -- point, which means that the rewrite might be incorrect. So if the |
| 225 | -- facts change, we need to rewrite the original block again (taking |
| 226 | -- into account the new facts). |
| 227 | let block = block_arr ! index |
| 228 | (new_block, out_facts) <- {-# SCC "do_block_rewrite" #-} |
| 229 | do_block block fbase1 |
| 230 | let blocks2 = mapInsert (entryLabel new_block) new_block blocks1 |
| 231 | (todo2, fbase2) = {-# SCC "mapFoldWithKey_rewrite" #-} |
| 232 | mapFoldlWithKey |
| 233 | (updateFact join dep_blocks) (todo1, fbase1) out_facts |
| 234 | loop todo2 blocks2 fbase2 |
| 235 | loop _ !blocks1 !fbase1 = return (blocks1, fbase1) |
| 236 | |
| 237 | |
| 238 | {- |
| 239 | Note [Unreachable blocks] |
| 240 | ~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 241 | A block that is not in the domain of tfb_fbase is "currently unreachable". |
| 242 | A currently-unreachable block is not even analyzed. Reason: consider |
| 243 | constant prop and this graph, with entry point L1: |
| 244 | L1: x:=3; goto L4 |
| 245 | L2: x:=4; goto L4 |
| 246 | L4: if x>3 goto L2 else goto L5 |
| 247 | Here L2 is actually unreachable, but if we process it with bottom input fact, |
| 248 | we'll propagate (x=4) to L4, and nuke the otherwise-good rewriting of L4. |
| 249 | |
| 250 | * If a currently-unreachable block is not analyzed, then its rewritten |
| 251 | graph will not be accumulated in tfb_rg. And that is good: |
| 252 | unreachable blocks simply do not appear in the output. |
| 253 | |
| 254 | * Note that clients must be careful to provide a fact (even if bottom) |
| 255 | for each entry point. Otherwise useful blocks may be garbage collected. |
| 256 | |
| 257 | * Note that updateFact must set the change-flag if a label goes from |
| 258 | not-in-fbase to in-fbase, even if its fact is bottom. In effect the |
| 259 | real fact lattice is |
| 260 | UNR |
| 261 | bottom |
| 262 | the points above bottom |
| 263 | |
| 264 | * Even if the fact is going from UNR to bottom, we still call the |
| 265 | client's fact_join function because it might give the client |
| 266 | some useful debugging information. |
| 267 | |
| 268 | * All of this only applies for *forward* ixpoints. For the backward |
| 269 | case we must treat every block as reachable; it might finish with a |
| 270 | 'return', and therefore have no successors, for example. |
| 271 | -} |
| 272 | |
| 273 | |
| 274 | ----------------------------------------------------------------------------- |
| 275 | -- Pieces that are shared by fixpoint and fixpoint_anal |
| 276 | ----------------------------------------------------------------------------- |
| 277 | |
| 278 | -- | Sort the blocks into the right order for analysis. This means reverse |
| 279 | -- postorder for a forward analysis. For the backward one, we simply reverse |
| 280 | -- that (see Note [Backward vs forward analysis]). |
| 281 | sortBlocks |
| 282 | :: NonLocal n |
| 283 | => Direction -> Label -> LabelMap (Block n C C) -> [Block n C C] |
| 284 | sortBlocks direction entry blockmap = |
| 285 | case direction of |
| 286 | Fwd -> fwd |
| 287 | Bwd -> reverse fwd |
| 288 | where |
| 289 | fwd = revPostorderFrom blockmap entry |
| 290 | |
| 291 | -- Note [Backward vs forward analysis] |
| 292 | -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 293 | -- The forward and backward cases are not dual. In the forward case, the entry |
| 294 | -- points are known, and one simply traverses the body blocks from those points. |
| 295 | -- In the backward case, something is known about the exit points, but a |
| 296 | -- backward analysis must also include reachable blocks that don't reach the |
| 297 | -- exit, as in a procedure that loops forever and has side effects.) |
| 298 | -- For instance, let E be the entry and X the exit blocks (arrows indicate |
| 299 | -- control flow) |
| 300 | -- E -> X |
| 301 | -- E -> B |
| 302 | -- B -> C |
| 303 | -- C -> B |
| 304 | -- We do need to include B and C even though they're unreachable in the |
| 305 | -- *reverse* graph (that we could use for backward analysis): |
| 306 | -- E <- X |
| 307 | -- E <- B |
| 308 | -- B <- C |
| 309 | -- C <- B |
| 310 | -- So when sorting the blocks for the backward analysis, we simply take the |
| 311 | -- reverse of what is used for the forward one. |
| 312 | |
| 313 | |
| 314 | -- | Construct a mapping from a @Label@ to the block indexes that should be |
| 315 | -- re-analyzed if the facts at that @Label@ change. |
| 316 | -- |
| 317 | -- Note that we're considering here the entry point of the block, so if the |
| 318 | -- facts change at the entry: |
| 319 | -- * for a backward analysis we need to re-analyze all the predecessors, but |
| 320 | -- * for a forward analysis, we only need to re-analyze the current block |
| 321 | -- (and that will in turn propagate facts into its successors). |
| 322 | mkDepBlocks :: NonLocal node => Direction -> [Block node C C] -> LabelMap IntSet |
| 323 | mkDepBlocks Fwd blocks = go blocks 0 mapEmpty |
| 324 | where |
| 325 | go [] !_ !dep_map = dep_map |
| 326 | go (b:bs) !n !dep_map = |
| 327 | go bs (n + 1) $ mapInsert (entryLabel b) (IntSet.singleton n) dep_map |
| 328 | mkDepBlocks Bwd blocks = go blocks 0 mapEmpty |
| 329 | where |
| 330 | go [] !_ !dep_map = dep_map |
| 331 | go (b:bs) !n !dep_map = |
| 332 | let insert m l = mapInsertWith IntSet.union l (IntSet.singleton n) m |
| 333 | in go bs (n + 1) $ foldl' insert dep_map (successors b) |
| 334 | |
| 335 | -- | After some new facts have been generated by analysing a block, we |
| 336 | -- fold this function over them to generate (a) a list of block |
| 337 | -- indices to (re-)analyse, and (b) the new FactBase. |
| 338 | updateFact |
| 339 | :: JoinFun f |
| 340 | -> LabelMap IntSet |
| 341 | -> (IntHeap, FactBase f) |
| 342 | -> Label |
| 343 | -> f -- out fact |
| 344 | -> (IntHeap, FactBase f) |
| 345 | updateFact fact_join dep_blocks (todo, fbase) lbl new_fact |
| 346 | = case lookupFact lbl fbase of |
| 347 | Nothing -> |
| 348 | -- See Note [No old fact] |
| 349 | let !z = mapInsert lbl new_fact fbase in (changed, z) |
| 350 | Just old_fact -> |
| 351 | case fact_join (OldFact old_fact) (NewFact new_fact) of |
| 352 | (NotChanged _) -> (todo, fbase) |
| 353 | (Changed f) -> let !z = mapInsert lbl f fbase in (changed, z) |
| 354 | where |
| 355 | changed = todo `IntSet.union` |
| 356 | mapFindWithDefault IntSet.empty lbl dep_blocks |
| 357 | |
| 358 | {- |
| 359 | Note [No old fact] |
| 360 | ~~~~~~~~~~~~~~~~~~ |
| 361 | We know that the new_fact is >= _|_, so we don't need to join. However, |
| 362 | if the new fact is also _|_, and we have already analysed its block, |
| 363 | we don't need to record a change. So there's a tradeoff here. It turns |
| 364 | out that always recording a change is faster. |
| 365 | -} |
| 366 | |
| 367 | ---------------------------------------------------------------- |
| 368 | -- Utilities |
| 369 | ---------------------------------------------------------------- |
| 370 | |
| 371 | -- Fact lookup: the fact `orelse` bottom |
| 372 | getFact :: DataflowLattice f -> Label -> FactBase f -> f |
| 373 | getFact lat l fb = case lookupFact l fb of Just f -> f |
| 374 | Nothing -> fact_bot lat |
| 375 | |
| 376 | -- | Returns the result of joining the facts from all the successors of the |
| 377 | -- provided node or block. |
| 378 | joinOutFacts :: (NonLocal n) => DataflowLattice f -> n e C -> FactBase f -> f |
| 379 | joinOutFacts lattice nonLocal fact_base = foldl' join (fact_bot lattice) facts |
| 380 | where |
| 381 | join new old = getJoined $ fact_join lattice (OldFact old) (NewFact new) |
| 382 | facts = |
| 383 | [ fromJust fact |
| 384 | | s <- successors nonLocal |
| 385 | , let fact = lookupFact s fact_base |
| 386 | , isJust fact |
| 387 | ] |
| 388 | |
| 389 | joinFacts :: DataflowLattice f -> [f] -> f |
| 390 | joinFacts lattice facts = foldl' join (fact_bot lattice) facts |
| 391 | where |
| 392 | join new old = getJoined $ fact_join lattice (OldFact old) (NewFact new) |
| 393 | |
| 394 | -- | Returns the joined facts for each label. |
| 395 | mkFactBase :: DataflowLattice f -> [(Label, f)] -> FactBase f |
| 396 | mkFactBase lattice = foldl' add mapEmpty |
| 397 | where |
| 398 | join = fact_join lattice |
| 399 | |
| 400 | add result (l, f1) = |
| 401 | let !newFact = |
| 402 | case mapLookup l result of |
| 403 | Nothing -> f1 |
| 404 | Just f2 -> getJoined $ join (OldFact f1) (NewFact f2) |
| 405 | in mapInsert l newFact result |
| 406 | |
| 407 | -- | Folds backward over all nodes of an open-open block. |
| 408 | -- Strict in the accumulator. |
| 409 | foldNodesBwdOO :: (node O O -> f -> f) -> Block node O O -> f -> f |
| 410 | foldNodesBwdOO funOO = go |
| 411 | where |
| 412 | go (BCat b1 b2) f = go b1 $! go b2 f |
| 413 | go (BSnoc h n) f = go h $! funOO n f |
| 414 | go (BCons n t) f = funOO n $! go t f |
| 415 | go (BMiddle n) f = funOO n f |
| 416 | go BNil f = f |
| 417 | {-# INLINABLE foldNodesBwdOO #-} |
| 418 | |
| 419 | -- | Folds backward over all the nodes of an open-open block and allows |
| 420 | -- rewriting them. The accumulator is both the block of nodes and @f@ (usually |
| 421 | -- dataflow facts). |
| 422 | -- Strict in both accumulated parts. |
| 423 | foldRewriteNodesBwdOO |
| 424 | :: forall f node. |
| 425 | (node O O -> f -> UniqDSM (Block node O O, f)) |
| 426 | -> Block node O O |
| 427 | -> f |
| 428 | -> UniqDSM (Block node O O, f) |
| 429 | foldRewriteNodesBwdOO rewriteOO initBlock initFacts = go initBlock initFacts |
| 430 | where |
| 431 | go (BCons node1 block1) !fact1 = (rewriteOO node1 `comp` go block1) fact1 |
| 432 | go (BSnoc block1 node1) !fact1 = (go block1 `comp` rewriteOO node1) fact1 |
| 433 | go (BCat blockA1 blockB1) !fact1 = (go blockA1 `comp` go blockB1) fact1 |
| 434 | go (BMiddle node) !fact1 = rewriteOO node fact1 |
| 435 | go BNil !fact = return (BNil, fact) |
| 436 | |
| 437 | comp rew1 rew2 = \f1 -> do |
| 438 | (b, f2) <- rew2 f1 |
| 439 | (a, !f3) <- rew1 f2 |
| 440 | let !c = joinBlocksOO a b |
| 441 | return (c, f3) |
| 442 | {-# INLINE comp #-} |
| 443 | {-# INLINABLE foldRewriteNodesBwdOO #-} |
| 444 | |
| 445 | joinBlocksOO :: Block n O O -> Block n O O -> Block n O O |
| 446 | joinBlocksOO BNil b = b |
| 447 | joinBlocksOO b BNil = b |
| 448 | joinBlocksOO (BMiddle n) b = blockCons n b |
| 449 | joinBlocksOO b (BMiddle n) = blockSnoc b n |
| 450 | joinBlocksOO b1 b2 = BCat b1 b2 |
| 451 | |
| 452 | type IntHeap = IntSet |