1 Ordering the compiler's passes
2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 1 Nov 94 * NB: if float-out is done after strictness, remember to
7 switch off demandedness flags on floated bindings!
8 13 Oct 94 * Run Float Inwards once more after strictness-simplify [andre]
9 4 Oct 94 * Do simplification between float-in and strictness [andre]
10 * Ignore-inline-pragmas flag for final simplification [andre]
12 Aug 94 Original: Simon, Andy, Andre
17 This ordering obeys all the constraints except (5)
18 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 simplify with foldr/build
27 [check FFT2 still gets benefits with this ordering]
29 =================================
31 =================================
33 1. float-in before strictness.
34 Reason: floating inwards moves definitions inwards to a site at which
35 the binding might well be strict.
37 Example let x = ... in
42 let y = let x = ... in x+1
45 The strictness analyser will do a better job of the latter
48 2. Don't simplify between float-in and strictness,
49 unless you disable float-let-out-of-let, otherwise
50 the simiplifier's local floating might undo some
53 Example let f = let y = .. in \x-> x+y
60 This is a bad move, because now y isn't strict.
61 In the pre-float case, the binding for y is strict.
62 Mind you, this isn't a very common case, and
63 it's easy to disable float-let-from-let.
65 3. Want full-laziness before foldr/build.
66 Reason: Give priority to sharing rather than deforestation.
68 Example \z -> let xs = build g
74 In the post-full-laziness case, xs is shared between all
75 applications of the function. If we did foldr/build
80 and now we can't share xs.
83 4. Want strictness after foldr/build.
84 Reason: foldr/build makes new function definitions which
85 can benefit from strictness analysis.
89 let g x a | x > 10 = a
90 | otherwise = g (x+1) (a+x)
92 Here we clearly want to get strictness analysis on g.
95 5. Want full laziness after strictness
96 Reason: absence may allow something to be floated out
97 which would not otherwise be.
99 Example \z -> let x = f (a,z) in ...
100 ===> (absence anal + inline wrapper of f)
101 \z -> let x = f.wrk a in ...
103 let x= f.wrk a in \z -> ...
105 TOO BAD. This doesn't look a common case to me.
108 6. Want float-in after foldr/build.
109 Reason: Desugaring list comprehensions + foldr/build
110 gives rise to new float-in opportunities.
112 Example ...some list comp...
117 (y:ys) -> ...(t v)...
122 (y:ys) -> ...(t v)...
124 Now v could usefully be floated into the second branch.
126 7. Want simplify after float-inwards.
127 [Occurred in the prelude, compiling ITup2.hs, function dfun.Ord.(*,*)]
128 This is due to the following (that happens with dictionaries):
130 let a1 = case v of (a,b) -> a
131 in let m1 = \ c -> case c of I# c# -> case c# of 1 -> a1 5
133 in let m2 = \ c -> case c of I# c# ->
134 case c# +# 1# of cc# -> let cc = I# cc#
138 floating inwards will push the definition of a1 into m1 (supposing
139 it is only used there):
141 in let m1 = let a1 = case v of (a,b) -> a
142 in \ c -> case c of I# c# -> case c# of 1 -> a1 5
144 in let m2 = \ c -> case c of I# c# ->
145 case c# +# 1# of cc# -> let cc = I# cc#
149 if we do strictness analysis now we will not get a worker-wrapper
150 for m1, because of the "let a1 ..." (notice that a1 is not strict in
153 Not having this worker wrapper might be very bad, because it might
154 mean that we will have to rebox arguments to m1 if they are
155 already unboxed, generating extra allocations, as occurs with m2 (cc)
158 To solve this problem we have decided to run the simplifier after
159 float-inwards, so that lets whose body is a HNF are floated out,
160 undoing the float-inwards transformation in these cases.
161 We are then back to the original code, which would have a worker-wrapper
162 for m1 after strictness analysis and would avoid the extra let in m2.
164 What we lose in this case are the opportunities for case-floating
165 that could be presented if, for example, a1 would indeed be demanded (strict)
166 after the floating inwards.
168 The only way of having the best of both is if we have the worker/wrapper
169 pass explicitly called, and then we could do with
175 worker-wrapper generation
178 a) be able to detect the strictness of m1 after the
179 first call to the strictness analyser, and exploit it with the simplifier
180 (in case it was strict).
181 b) after the call to the simplifier (if m1 was not demanded)
182 it would be floated out just like we currently do, before stricness
183 analysis II and worker/wrapperisation.
185 The reason to not do worker/wrapperisation twice is to avoid
186 generating wrappers for wrappers which could happen.
189 8. If full laziness is ever done after strictness, remember to switch off
190 demandedness flags on floated bindings! This isn't done at the moment.
193 Ignore-inline-pragmas flag for final simplification
194 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195 [Occurred in the prelude, compiling ITup2.hs, function dfun.Ord.(*,*)]
196 Sometimes (e.g. in dictionary methods) we generate
197 worker/wrappers for functions but the wrappers are never
198 inlined. In dictionaries we often have
205 and if we create worker/wrappers for f1,...,fn the wrappers will not
206 be inlined anywhere, and we will have ended up with extra
207 closures (one for the worker and one for the wrapper) and extra
208 function calls, as when we access the dictionary we will be acessing
209 the wrapper, which will call the worker.
210 The simplifier never inlines workers into wrappers, as the wrappers
211 themselves have INLINE pragmas attached to them (so that they are always
212 inlined, and we do not know in advance how many times they will be inlined).
214 To solve this problem, in the last call to the simplifier we will
215 ignore these inline pragmas and handle the workers and the wrappers
216 as normal definitions. This will allow a worker to be inlined into
217 the wrapper if it satisfies all the criteria for inlining (e.g. it is
218 the only occurrence of the worker etc.).
220 Run Float Inwards once more after strictness-simplify
221 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
222 [Occurred in the prelude, compiling IInt.hs, function const.Int.index.wrk]
223 When workers are generated after strictness analysis (worker/wrapper),
224 we generate them with "reboxing" lets, that simply reboxes the unboxed
225 arguments, as it may be the case that the worker will need the
226 original boxed value:
230 (c,d) -> case a == c of
232 False -> ((1,1),(2,2))
236 f_wrapper x y = case x of
238 (c,d) -> f_worker a b c d
240 f_worker a b c d = let x = (a,b)
244 False -> ((1,1),(2,2))
246 in this case the simplifier will remove the binding for y as it is not
247 used (we expected this to happen very often, but we do not know how
248 many "reboxers" are eventually removed and how many are kept), and
249 will keep the binding for x. But notice that x is only used in *one*
250 of the branches in the case, but is always being allocated! The
251 floating inwards pass would push its definition into the True branch.
252 A similar benefit occurs if it is only used inside a let definition.
253 These are basically the advantages of floating inwards, but they are
254 only exposed after the S.A./worker-wrapperisation of the code! As we
255 also have reasons to float inwards before S.A. we have to run it