Skip to content

Commit bbe184f

Browse files
committed
Add new simplification rule for invariant loop parameters.
This was suggested by Cosmin to address some of the code produced by AD. The idea is that for a loop of the form loop p = x ... ...stms... in res we construct and simplify the body let p = x ...stms... in res and if that simplifies to 'x', then we conclude that the loop parameter 'p' must be invariant to the loop and simply bind it (and the loop result) to 'x'. Complication: for multi-parameter loops, we must also check that the *original* computation of 'res' does *only* depends on other invariant loop parameters. Currently we do this only for loops that have a constant as one of their initial loop parameter values. The main downside of this rule is that doing recursive simplification is quite expensive. Especially after sequentialisation, pretty much every 'reduce' will have been turned into a loop that triggers this rule (although the rule itself will fail in most cases, after doing the simplification). Therefore I'm a bit hesitant to enable it as is. Sure, the Futhark compiler is slow and it was never meant to be fast, but it is still quite easy for the compiler to become *uselessly slow* if we are not careful. E.g. on OptionPricing, this rule itself makes compilation 10% slower (and does not actually optimise anything - this is purely the cost of failing checks).
1 parent 3298c56 commit bbe184f

6 files changed

Lines changed: 175 additions & 42 deletions

File tree

src/Futhark/Optimise/Simplify/Engine.hs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ module Futhark.Optimise.Simplify.Engine
5454
bindLParams,
5555
simplifyBody,
5656
ST.SymbolTable,
57-
hoistStms,
5857
blockIf,
5958
blockMigrated,
6059
enterLoop,
@@ -206,6 +205,18 @@ asksEngineEnv f = f <$> askEngineEnv
206205
askVtable :: SimpleM rep (ST.SymbolTable (Wise rep))
207206
askVtable = asksEngineEnv envVtable
208207

208+
mkSubSimplify :: SimplifiableRep rep => SimpleM rep (SubSimplify (Wise rep))
209+
mkSubSimplify = do
210+
(ops, env) <- ask
211+
pure $ \body -> do
212+
scope <- askScope
213+
let env' = env {envVtable = ST.fromScope scope}
214+
(x, _) <- modifyNameSource $ runSimpleM (f body) ops env'
215+
pure x
216+
where
217+
f body =
218+
simplifyBodyNoHoisting mempty (map (const mempty) (bodyResult body)) body
219+
209220
localVtable ::
210221
(ST.SymbolTable (Wise rep) -> ST.SymbolTable (Wise rep)) ->
211222
SimpleM rep a ->
@@ -486,7 +497,8 @@ hoistStms rules block orig_stms final = do
486497

487498
process usageInStm stm stms usage x = do
488499
vtable <- askVtable
489-
res <- bottomUpSimplifyStm rules (vtable, usage) stm
500+
ss <- mkSubSimplify
501+
res <- bottomUpSimplifyStm ss rules (vtable, usage) stm
490502
case res of
491503
Nothing -- Nothing to optimise - see if hoistable.
492504
| block vtable usage stm ->
@@ -517,7 +529,8 @@ hoistStms rules block orig_stms final = do
517529
stms_h' <- nonrecSimplifyStm stms_h
518530

519531
vtable <- askVtable
520-
simplified <- topDownSimplifyStm rules vtable stms_h'
532+
ss <- mkSubSimplify
533+
simplified <- topDownSimplifyStm ss rules vtable stms_h'
521534

522535
case simplified of
523536
Just newstms -> do

src/Futhark/Optimise/Simplify/Rule.hs

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module Futhark.Optimise.Simplify.Rule
1616
RuleM,
1717
cannotSimplify,
1818
liftMaybe,
19+
SubSimplify,
20+
subSimplify,
1921

2022
-- * Rule definition
2123
Rule (..),
@@ -53,19 +55,26 @@ module Futhark.Optimise.Simplify.Rule
5355
)
5456
where
5557

58+
import Control.Monad.Reader
5659
import Control.Monad.State
5760
import Futhark.Analysis.SymbolTable qualified as ST
5861
import Futhark.Analysis.UsageTable qualified as UT
5962
import Futhark.Builder
6063
import Futhark.IR
6164

65+
-- | An action for recursively simplifying a body.
66+
type SubSimplify rep = Body rep -> RuleM rep (Body rep)
67+
68+
newtype RuleEnv rep = RuleEnv {envSubSimplify :: SubSimplify rep}
69+
6270
-- | The monad in which simplification rules are evaluated.
63-
newtype RuleM rep a = RuleM (BuilderT rep (StateT VNameSource Maybe) a)
71+
newtype RuleM rep a = RuleM (BuilderT rep (StateT VNameSource (ReaderT (RuleEnv rep) Maybe)) a)
6472
deriving
6573
( Functor,
6674
Applicative,
6775
Monad,
6876
MonadFreshNames,
77+
MonadReader (RuleEnv rep),
6978
HasScope rep,
7079
LocalScope rep
7180
)
@@ -84,19 +93,29 @@ instance (BuilderOps rep) => MonadBuilder (RuleM rep) where
8493
simplify ::
8594
Scope rep ->
8695
VNameSource ->
96+
RuleEnv rep ->
8797
Rule rep ->
8898
Maybe (Stms rep, VNameSource)
89-
simplify _ _ Skip = Nothing
90-
simplify scope src (Simplify (RuleM m)) =
91-
runStateT (runBuilderT_ m scope) src
99+
simplify _ _ _ Skip = Nothing
100+
simplify scope src env (Simplify (RuleM m)) =
101+
runReaderT (runStateT (runBuilderT_ m scope) src) env
92102

103+
-- | Abort the current attempt at simplification.
93104
cannotSimplify :: RuleM rep a
94-
cannotSimplify = RuleM $ lift $ lift Nothing
105+
cannotSimplify = RuleM $ lift $ lift $ lift Nothing
95106

96107
liftMaybe :: Maybe a -> RuleM rep a
97108
liftMaybe Nothing = cannotSimplify
98109
liftMaybe (Just x) = pure x
99110

111+
-- | Recursively apply the simplifier on this body, using the current
112+
-- rulebook. This can be quite costly, so think carefully before
113+
-- doing this.
114+
subSimplify :: SubSimplify rep
115+
subSimplify body = do
116+
s <- asks envSubSimplify
117+
s body
118+
100119
-- | An efficient way of encoding whether a simplification rule should even be attempted.
101120
data Rule rep
102121
= -- | Give it a shot.
@@ -252,31 +271,6 @@ ruleBook topdowns bottomups =
252271
forOp RuleGeneric {} = True
253272
forOp _ = False
254273

255-
-- | @simplifyStm lookup stm@ performs simplification of the
256-
-- binding @stm@. If simplification is possible, a replacement list
257-
-- of bindings is returned, that bind at least the same names as the
258-
-- original binding (and possibly more, for intermediate results).
259-
topDownSimplifyStm ::
260-
(MonadFreshNames m, HasScope rep m, PrettyRep rep) =>
261-
RuleBook rep ->
262-
ST.SymbolTable rep ->
263-
Stm rep ->
264-
m (Maybe (Stms rep))
265-
topDownSimplifyStm = applyRules . bookTopDownRules
266-
267-
-- | @simplifyStm uses stm@ performs simplification of the binding
268-
-- @stm@. If simplification is possible, a replacement list of
269-
-- bindings is returned, that bind at least the same names as the
270-
-- original binding (and possibly more, for intermediate results).
271-
-- The first argument is the set of names used after this binding.
272-
bottomUpSimplifyStm ::
273-
(MonadFreshNames m, HasScope rep m, PrettyRep rep) =>
274-
RuleBook rep ->
275-
(ST.SymbolTable rep, UT.UsageTable) ->
276-
Stm rep ->
277-
m (Maybe (Stms rep))
278-
bottomUpSimplifyStm = applyRules . bookBottomUpRules
279-
280274
rulesForStm :: Stm rep -> Rules rep a -> [SimplificationRule rep a]
281275
rulesForStm stm = case stmExp stm of
282276
BasicOp {} -> rulesBasicOp
@@ -299,19 +293,47 @@ applyRule _ _ _ =
299293

300294
applyRules ::
301295
(MonadFreshNames m, HasScope rep m, PrettyRep rep) =>
296+
SubSimplify rep ->
302297
Rules rep a ->
303298
a ->
304299
Stm rep ->
305300
m (Maybe (Stms rep))
306-
applyRules all_rules context stm = do
301+
applyRules ss all_rules context stm = do
307302
scope <- askScope
308-
303+
let env = RuleEnv ss
309304
modifyNameSource $ \src ->
310305
let applyRules' [] = Nothing
311306
applyRules' (rule : rules) =
312-
case simplify scope src (applyRule rule context stm) of
307+
case simplify scope src env (applyRule rule context stm) of
313308
Just x -> Just x
314309
Nothing -> applyRules' rules
315310
in case applyRules' $ rulesForStm stm all_rules of
316311
Just (stms, src') -> (Just stms, src')
317312
Nothing -> (Nothing, src)
313+
314+
-- | @simplifyStm lookup stm@ performs simplification of the
315+
-- binding @stm@. If simplification is possible, a replacement list
316+
-- of bindings is returned, that bind at least the same names as the
317+
-- original binding (and possibly more, for intermediate results).
318+
topDownSimplifyStm ::
319+
(MonadFreshNames m, HasScope rep m, PrettyRep rep) =>
320+
SubSimplify rep ->
321+
RuleBook rep ->
322+
ST.SymbolTable rep ->
323+
Stm rep ->
324+
m (Maybe (Stms rep))
325+
topDownSimplifyStm ss = applyRules ss . bookTopDownRules
326+
327+
-- | @simplifyStm uses stm@ performs simplification of the binding
328+
-- @stm@. If simplification is possible, a replacement list of
329+
-- bindings is returned, that bind at least the same names as the
330+
-- original binding (and possibly more, for intermediate results).
331+
-- The first argument is the set of names used after this binding.
332+
bottomUpSimplifyStm ::
333+
(MonadFreshNames m, HasScope rep m, PrettyRep rep) =>
334+
SubSimplify rep ->
335+
RuleBook rep ->
336+
(ST.SymbolTable rep, UT.UsageTable) ->
337+
Stm rep ->
338+
m (Maybe (Stms rep))
339+
bottomUpSimplifyStm ss = applyRules ss . bookBottomUpRules

src/Futhark/Optimise/Simplify/Rules/Loop.hs

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
module Futhark.Optimise.Simplify.Rules.Loop (loopRules) where
33

44
import Control.Monad
5-
import Data.Bifunctor (second)
5+
import Data.Bifunctor (first, second)
66
import Data.List (partition)
7+
import Data.Map qualified as M
78
import Data.Maybe
89
import Futhark.Analysis.DataDependencies
910
import Futhark.Analysis.PrimExp.Convert
@@ -83,6 +84,79 @@ removeRedundantMergeVariables (_, used) pat aux (merge, form, body)
8384
removeRedundantMergeVariables _ _ _ _ =
8485
Skip
8586

87+
-- For a loop of the form
88+
--
89+
-- loop p = x ...
90+
-- ...stms...
91+
-- in res
92+
--
93+
-- we construct and simplify the body
94+
--
95+
-- let p = x
96+
-- ...stms...
97+
-- in res
98+
--
99+
-- and if that simplifies to 'x', then we conclude that the loop
100+
-- parameter 'p' must be invariant to the loop and simply bind it (and
101+
-- the loop result) to 'x'.
102+
--
103+
-- Complication: for multi-parameter loops, we must also check that
104+
-- the *original* computation of 'res' does *only* depends on other
105+
-- invariant loop parameters. See tests/loops/invariant1.fut for an
106+
-- example.
107+
simplifyInvariantParams :: BuilderOps rep => TopDownRuleDoLoop rep
108+
simplifyInvariantParams _vtable pat aux (params, form, loopbody)
109+
| consts <- filter constInit params,
110+
not $ null consts = Simplify . auxing aux $
111+
localScope (scopeOfFParams (map fst params) <> scopeOf form) $ do
112+
loopbody_simpl <- subSimplify <=< buildBody_ $ do
113+
mapM_ bindParam consts
114+
bodyBind loopbody
115+
let inv_pnames = determineInvariant $ bodyResult loopbody_simpl
116+
invariant (_, (p, _), _) = paramName p `elem` inv_pnames
117+
(inv, var) =
118+
partition invariant $
119+
zip3 (patElems pat) params (bodyResult loopbody)
120+
(var_pes, var_params, var_res) = unzip3 var
121+
when (null inv) cannotSimplify
122+
mapM_ bindInv inv
123+
loopbody' <- mkBodyM (bodyStms loopbody) var_res
124+
letBind (Pat var_pes) $ DoLoop var_params form loopbody'
125+
| otherwise = Skip
126+
where
127+
loopbody_deps = dataDependencies loopbody
128+
resDep (Var v) = oneName v <> fromMaybe mempty (M.lookup v loopbody_deps)
129+
resDep _ = mempty
130+
res_deps = map (resDep . resSubExp) $ bodyResult loopbody
131+
132+
constInit (_, Constant {}) = True
133+
constInit _ = False
134+
135+
bindParam (p, se) = letBindNames [paramName p] $ BasicOp $ SubExp se
136+
137+
bindInv (pe, (p, se), _) = do
138+
letBindNames [patElemName pe] $ BasicOp $ SubExp se
139+
letBindNames [paramName p] $ BasicOp $ SubExp se
140+
141+
resIsInvariant ((_, x), x') = x == resSubExp x'
142+
143+
depOnVar var (_, deps) = any (`nameIn` deps) var
144+
145+
noInvDepOnVar inv var
146+
| (inv_var, inv') <- partition (depOnVar var) inv,
147+
not $ null inv_var =
148+
noInvDepOnVar inv' $ map fst inv_var <> var
149+
| otherwise =
150+
map fst inv
151+
152+
determineInvariant simpl_res =
153+
let (inv, var) =
154+
partition (resIsInvariant . fst) $
155+
zip (zip (map (first paramName) params) simpl_res) res_deps
156+
in noInvDepOnVar
157+
(map (first (fst . fst)) inv)
158+
(map (fst . fst . fst) var)
159+
86160
-- We may change the type of the loop if we hoist out a shape
87161
-- annotation, in which case we also need to tweak the bound pattern.
88162
hoistLoopInvariantMergeVariables :: BuilderOps rep => TopDownRuleDoLoop rep
@@ -290,7 +364,8 @@ topDownRules =
290364
[ RuleDoLoop hoistLoopInvariantMergeVariables,
291365
RuleDoLoop simplifyClosedFormLoop,
292366
RuleDoLoop simplifyKnownIterationLoop,
293-
RuleDoLoop simplifyLoopVariables
367+
RuleDoLoop simplifyLoopVariables,
368+
RuleDoLoop simplifyInvariantParams
294369
]
295370

296371
bottomUpRules :: BuilderOps rep => [BottomUpRule rep]

tests/loops/invariant0.fut

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Removal of invariant of invariant loop parameter (and eventually entire loop).
2+
-- ==
3+
-- structure { DoLoop 0 }
4+
5+
entry main [n] (bs: [n]bool) =
6+
let res =
7+
loop (x, y) = (0i32, false)
8+
for i < n do
9+
let y' = bs[i] && y
10+
let x' = x + (i32.bool y')
11+
in (x', y')
12+
in res

tests/loops/invariant1.fut

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- Not actually invariant if you look carefully!
2+
-- ==
3+
-- input { 0 } output { 0 false }
4+
-- input { 4 } output { 3 true }
5+
6+
entry main (n: i32) =
7+
let res =
8+
loop (x, y) = (0i32, false)
9+
for _i < n do
10+
let x' = if y then x + 1 else x
11+
let y' = y || true
12+
in (x', y')
13+
in res
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1+
-- Also not actually invariant.
12
-- ==
23
-- input { 0 } output { 1 }
34
-- input { 10 } output { 89 }
45

5-
6-
def fib(n: i32): i32 =
6+
entry main (n: i32) =
77
let (x,_) = loop (x, y) = (1,1) for _i < n do (y, x+y)
88
in x
9-
10-
def main(n: i32): i32 = fib(n)

0 commit comments

Comments
 (0)