5 , dualLiveLattice, dualLiveness
6 , insertSpillsAndReloads --- XXX todo check live-in at entry against formals
7 , dualLivenessWithInsertion
8 , spillAndReloadComments
13 , removeDeadAssignmentsAndReloads
29 import Outputable hiding (empty)
30 import qualified Outputable as PP
35 import Prelude hiding (zip)
37 -- The point of this module is to insert spills and reloads to
38 -- establish the invariant that at a call (or at any proc point with
39 -- an established protocol) all live variables not expected in
40 -- registers are sitting on the stack. We use a backward analysis to
41 -- insert spills and reloads. It should some day be followed by a
42 -- forward transformation to sink reloads as deeply as possible, so as
43 -- to reduce register pressure.
45 data ExtendWithSpills m
50 type M = ExtendWithSpills Middle
52 -- A variable can be expected to be live in a register, live on the
53 -- stack, or both. This analysis ensures that spills and reloads are
54 -- inserted as needed to make sure that every live variable needed
55 -- after a call is available on the stack. Spills are pushed back to
56 -- their reaching definitions, but reloads are dropped wherever needed
57 -- and will have to be sunk by a later forward transformation.
59 data DualLive = DualLive { on_stack :: RegSet, in_regs :: RegSet }
61 dualUnion :: DualLive -> DualLive -> DualLive
62 dualUnion (DualLive s r) (DualLive s' r') =
63 DualLive (s `unionUniqSets` s') (r `unionUniqSets` r')
65 dualUnionList :: [DualLive] -> DualLive
66 dualUnionList ls = DualLive ss rs
67 where ss = unionManyUniqSets $ map on_stack ls
68 rs = unionManyUniqSets $ map in_regs ls
70 _changeStack, changeRegs :: (RegSet -> RegSet) -> DualLive -> DualLive
71 _changeStack f live = live { on_stack = f (on_stack live) }
72 changeRegs f live = live { in_regs = f (in_regs live) }
75 dualLiveLattice :: DataflowLattice DualLive
77 DataflowLattice "variables live in registers and on stack" empty add False
78 where empty = DualLive emptyRegSet emptyRegSet
79 -- | compute in the Tx monad to track whether anything has changed
80 add new old = do stack <- add1 (on_stack new) (on_stack old)
81 regs <- add1 (in_regs new) (in_regs old)
82 return $ DualLive stack regs
83 add1 = fact_add_to liveLattice
85 dualLivenessWithInsertion :: BPass M Last DualLive
86 dualLivenessWithInsertion = a_ft_b_unlimited dualLiveness insertSpillsAndReloads
88 dualLiveness :: BAnalysis M Last DualLive
89 dualLiveness = BComp "dual liveness" exit last middle first
91 last = lastDualLiveness
92 middle = middleDualLiveness
94 empty = fact_bot dualLiveLattice
96 -- ^ could take a proc-point set and choose to spill here,
97 -- but it's probably better to run this pass, choose
98 -- proc-point protocols, insert more CopyIn nodes, and run
101 middleDualLiveness :: DualLive -> M -> DualLive
102 middleDualLiveness live (Spill regs) = live'
103 -- live-in on-stack requirements are satisfied;
104 -- live-out in-regs obligations are created
105 where live' = DualLive { on_stack = on_stack live `minusRegSet` regs
106 , in_regs = in_regs live `plusRegSet` regs }
108 middleDualLiveness live (Reload regs) = live'
109 -- live-in in-regs requirements are satisfied;
110 -- live-out on-stack obligations are created
111 where live' = DualLive { on_stack = on_stack live `plusRegSet` regs
112 , in_regs = in_regs live `minusRegSet` regs }
114 middleDualLiveness live (NotSpillOrReload m) = changeRegs (middleLiveness m) live
116 lastDualLiveness :: (BlockId -> DualLive) -> Last -> DualLive
117 lastDualLiveness env l = last l
118 where last (LastReturn) = empty
119 last (LastJump e) = changeRegs (gen e) empty
120 last (LastBranch id) = env id
121 last (LastCall tgt Nothing) = changeRegs (gen tgt) empty
122 last (LastCall tgt (Just k)) =
123 -- nothing can be live in registers at this point
125 if isEmptyUniqSet (in_regs live) then
126 DualLive (on_stack live) (gen tgt emptyRegSet)
128 panic "live values in registers at call continuation"
129 last (LastCondBranch e t f) = changeRegs (gen e) $ dualUnion (env t) (env f)
130 last (LastSwitch e tbl) = changeRegs (gen e) $ dualUnionList $
131 map env (catMaybes tbl)
132 empty = fact_bot dualLiveLattice
134 gen, kill :: UserOfLocalRegs a => a -> RegSet -> RegSet
135 gen a live = foldRegsUsed extendRegSet live a
136 kill a live = foldRegsUsed delOneFromUniqSet live a
138 insertSpillsAndReloads :: BFunctionalTransformation M Last DualLive
139 insertSpillsAndReloads = BComp "CPS spiller" exit last middle first
141 last = \_ _ -> Nothing
142 middle = middleInsertSpillsAndReloads
144 -- ^ could take a proc-point set and choose to spill here,
145 -- but it's probably better to run this pass, choose
146 -- proc-point protocols, insert more CopyIn nodes, and run
150 middleInsertSpillsAndReloads :: DualLive -> M -> Maybe (Graph M Last)
151 middleInsertSpillsAndReloads _ (Spill _) = Nothing
152 middleInsertSpillsAndReloads _ (Reload _) = Nothing
153 middleInsertSpillsAndReloads live m@(NotSpillOrReload nsr) = middle nsr
154 where middle (MidAssign (CmmLocal reg) _) =
155 if reg `elemRegSet` on_stack live then -- must spill
156 my_trace "Spilling" (f4sep [text "spill" <+> ppr reg,
157 text "after", ppr m]) $
158 Just $ graphOfMiddles [m, Spill $ mkRegSet [reg]]
161 middle (CopyIn _ formals _) =
162 -- only 'formals' can be in regs at this point
163 let regs' = kill formals (in_regs live) -- live in regs; must reload
164 is_stack_var r = elemRegSet r (on_stack live)
165 needs_spilling = filterRegsUsed is_stack_var formals
166 -- a formal that is expected on the stack; must spill
167 in if isEmptyUniqSet regs' && isEmptyUniqSet needs_spilling then
170 let code = if isEmptyUniqSet regs' then []
171 else Reload regs' : []
172 code' = if isEmptyUniqSet needs_spilling then code
173 else Spill needs_spilling : code
175 my_trace "At CopyIn" (f4sep [text "Triggered by ", ppr live,
176 ppr (Reload regs' :: M),
177 ppr (Spill needs_spilling :: M),
178 text "after", ppr m]) $
179 Just $ graphOfMiddles (m : code')
182 -- | For conversion back to vanilla C--
183 spillAndReloadComments :: M -> Middle
184 spillAndReloadComments (NotSpillOrReload m) = m
185 spillAndReloadComments (Spill regs) = show_regs "Spill" regs
186 spillAndReloadComments (Reload regs) = show_regs "Reload" regs
188 show_regs :: String -> RegSet -> Middle
189 show_regs s regs = MidComment $ mkFastString $ showSDoc $ ppr_regs s regs
192 ----------------------------------------------------------------
195 -- The idea is to compute at each point the set of registers such that
196 -- on every path to the point, the register is defined by a Reload
197 -- instruction. Then, if a use appears at such a point, we can safely
198 -- insert a Reload right before the use. Finally, we can eliminate
199 -- the early reloads along with other dead assignments.
201 data AvailRegs = UniverseMinus RegSet
205 availRegsLattice :: DataflowLattice AvailRegs
206 availRegsLattice = DataflowLattice "register gotten from reloads" empty add True
207 where empty = UniverseMinus emptyRegSet
208 -- | compute in the Tx monad to track whether anything has changed
210 let join = interAvail new old in
211 if join `smallerAvail` old then aTx join else noTx join
214 interAvail :: AvailRegs -> AvailRegs -> AvailRegs
215 interAvail (UniverseMinus s) (UniverseMinus s') = UniverseMinus (s `plusRegSet` s')
216 interAvail (AvailRegs s) (AvailRegs s') = AvailRegs (s `timesRegSet` s')
217 interAvail (AvailRegs s) (UniverseMinus s') = AvailRegs (s `minusRegSet` s')
218 interAvail (UniverseMinus s) (AvailRegs s') = AvailRegs (s' `minusRegSet` s )
220 smallerAvail :: AvailRegs -> AvailRegs -> Bool
221 smallerAvail (AvailRegs _) (UniverseMinus _) = True
222 smallerAvail (UniverseMinus _) (AvailRegs _) = False
223 smallerAvail (AvailRegs s) (AvailRegs s') = sizeUniqSet s < sizeUniqSet s'
224 smallerAvail (UniverseMinus s) (UniverseMinus s') = sizeUniqSet s > sizeUniqSet s'
226 extendAvail :: AvailRegs -> LocalReg -> AvailRegs
227 extendAvail (UniverseMinus s) r = UniverseMinus (deleteFromRegSet s r)
228 extendAvail (AvailRegs s) r = AvailRegs (extendRegSet s r)
230 deleteFromAvail :: AvailRegs -> LocalReg -> AvailRegs
231 deleteFromAvail (UniverseMinus s) r = UniverseMinus (extendRegSet s r)
232 deleteFromAvail (AvailRegs s) r = AvailRegs (deleteFromRegSet s r)
234 elemAvail :: AvailRegs -> LocalReg -> Bool
235 elemAvail (UniverseMinus s) r = not $ elemRegSet r s
236 elemAvail (AvailRegs s) r = elemRegSet r s
238 cmmAvailableReloads :: LGraph M Last -> BlockEnv AvailRegs
239 cmmAvailableReloads g = env
240 where env = runDFA availRegsLattice $
241 do run_f_anal transfer (fact_bot availRegsLattice) g
243 transfer :: FAnalysis M Last AvailRegs
244 transfer = FComp "available-reloads analysis" first middle last exit
245 exit _ = LastOutFacts []
246 first avail _ = avail
247 middle = flip middleAvail
251 -- | The transfer equations use the traditional 'gen' and 'kill'
252 -- notations, which should be familiar from the dragon book.
253 agen, akill :: UserOfLocalRegs a => a -> AvailRegs -> AvailRegs
254 agen a live = foldRegsUsed extendAvail live a
255 akill a live = foldRegsUsed deleteFromAvail live a
257 middleAvail :: M -> AvailRegs -> AvailRegs
258 middleAvail (Spill _) = id
259 middleAvail (Reload regs) = agen regs
260 middleAvail (NotSpillOrReload m) = middle m
261 where middle (MidComment {}) = id
262 middle (MidAssign lhs _expr) = akill lhs
263 middle (MidStore {}) = id
264 middle (MidUnsafeCall _tgt ress _args) = akill ress
265 middle (MidAddToContext {}) = id
266 middle (CopyIn _ formals _) = akill formals
267 middle (CopyOut {}) = id
269 lastAvail :: AvailRegs -> Last -> LastOutFacts AvailRegs
270 lastAvail _ (LastCall _ (Just k)) = LastOutFacts [(k, AvailRegs emptyRegSet)]
271 lastAvail avail l = LastOutFacts $ map (\id -> (id, avail)) $ succs l
273 insertLateReloads :: LGraph M Last -> DFTx (LGraph M Last)
274 insertLateReloads g = mapM_blocks insertM g
275 where env = cmmAvailableReloads g
276 avail id = lookupBlockEnv env id `orElse` AvailRegs emptyRegSet
277 insertM b = functionalDFTx "late reloads" (insert b)
278 insert (Block id tail) fuel = propagate (ZFirst id) (avail id) tail fuel
279 propagate h avail (ZTail m t) fuel =
280 let (h', fuel') = maybe_add_reload h avail m fuel in
281 propagate (ZHead h' m) (middleAvail m avail) t fuel'
282 propagate h avail (ZLast l) fuel =
283 let (h', fuel') = maybe_add_reload h avail l fuel in
284 (zipht h' (ZLast l), fuel')
285 maybe_add_reload h avail node fuel =
286 let used = filterRegsUsed (elemAvail avail) node
287 in if fuel == 0 || isEmptyUniqSet used then (h, fuel)
288 else (ZHead h (Reload used), fuel-1)
291 _lateReloadsWithoutFuel :: LGraph M Last -> LGraph M Last
292 _lateReloadsWithoutFuel g = map_blocks insert g
293 where env = cmmAvailableReloads g
294 avail id = lookupBlockEnv env id `orElse` AvailRegs emptyRegSet
295 insert (Block id tail) = propagate (ZFirst id) (avail id) tail
296 propagate h avail (ZTail m t) =
297 propagate (ZHead (maybe_add_reload h avail m) m) (middleAvail m avail) t
298 propagate h avail (ZLast l) =
299 zipht (maybe_add_reload h avail l) (ZLast l)
300 maybe_add_reload h avail node =
301 let used = filterRegsUsed (elemAvail avail) node
302 in if isEmptyUniqSet used then h
303 else ZHead h (Reload used)
306 removeDeadAssignmentsAndReloads :: BPass M Last DualLive
307 removeDeadAssignmentsAndReloads = a_ft_b dualLiveness remove_deads
308 where remove_deads = BComp "dead-assignment & -reload elim" exit last middle first
310 last = \_ _ -> Nothing
311 middle = middleRemoveDeads
314 middleRemoveDeads :: DualLive -> M -> Maybe (Graph M Last)
315 middleRemoveDeads _ (Spill _) = Nothing
316 middleRemoveDeads live (Reload s) =
317 if sizeUniqSet worth_reloading < sizeUniqSet s then
318 Just $ if isEmptyUniqSet worth_reloading then emptyGraph
319 else graphOfMiddles [Reload worth_reloading]
322 where worth_reloading = intersectUniqSets s (in_regs live)
323 middleRemoveDeads live (NotSpillOrReload m) = middle m
324 where middle (MidAssign (CmmLocal reg') _)
325 | not (reg' `elemRegSet` in_regs live) = Just emptyGraph
330 ---------------------
333 instance UserOfLocalRegs m => UserOfLocalRegs (ExtendWithSpills m) where
334 foldRegsUsed f z (Spill regs) = foldRegsUsed f z regs
335 foldRegsUsed _f z (Reload _) = z
336 foldRegsUsed f z (NotSpillOrReload m) = foldRegsUsed f z m
338 ---------------------
341 instance Outputable m => Outputable (ExtendWithSpills m) where
342 ppr (Spill regs) = ppr_regs "Spill" regs
343 ppr (Reload regs) = ppr_regs "Reload" regs
344 ppr (NotSpillOrReload m) = ppr m
346 instance Outputable (LGraph M Last) where
349 instance DebugNodes M Last
351 ppr_regs :: String -> RegSet -> SDoc
352 ppr_regs s regs = text s <+> commafy (map ppr $ uniqSetToList regs)
353 where commafy xs = hsep $ punctuate comma xs
355 instance Outputable DualLive where
356 ppr (DualLive {in_regs = regs, on_stack = stack}) =
357 if isEmptyUniqSet regs && isEmptyUniqSet stack then
358 text "<nothing-live>"
360 nest 2 $ fsep [if isEmptyUniqSet regs then PP.empty
361 else (ppr_regs "live in regs =" regs),
362 if isEmptyUniqSet stack then PP.empty
363 else (ppr_regs "live on stack =" stack)]
365 instance Outputable AvailRegs where
366 ppr (UniverseMinus s) = if isEmptyUniqSet s then text "<everything available>"
367 else ppr_regs "available = all but" s
368 ppr (AvailRegs s) = if isEmptyUniqSet s then text "<nothing available>"
369 else ppr_regs "available = " s
371 my_trace :: String -> SDoc -> a -> a
372 my_trace = if False then pprTrace else \_ _ a -> a
374 f4sep :: [SDoc] -> SDoc
376 f4sep (d:ds) = fsep (d : map (nest 4) ds)