on master help sign in
update/clarify/reorganize inlining doc
e51d5f0Harendra Kumar
design/inlining.md+58−36
@@ -10,26 +10,39 @@ i.e. phase 0, 1 and 2. We have defined them as follows in `inline.h`:
10#define INLINE_LATE INLINE [0]
10#define INLINE_LATE INLINE [0]
11```
11```
12
12
13+## Low Level `fromStreamD/toStreamD` Fusion
14+
13−* The combinators in `Streamly.Prelude` are defined in terms of combinators in
15+The combinators in `Streamly.Prelude` are defined in terms of combinators in
14− `Streamly.Streams.StreamD` (Direct style streams) or
16+`Streamly.Streams.StreamD` (Direct style streams) or `Streamly.Streams.StreamK`
15− `Streamly.Streams.StreamK` (CPS style streams). We convert the stream from
17+(CPS style streams). We convert the stream from `StreamD` to `StreamK`
16− `StreamD` to `StreamK` representation or vice versa in some cases. In the
18+representation or vice versa in some cases.
19+
17− first inlining phase we expand these combinators and apply the rewrite rules
20+In the first inlining phase (INLINE_EARLY or INLINE) we expand
21+the combinators in `Streamly.Prelude` into
22+fromStreamD/fromStreamK/toStreamD/toStreamK and combinators defined in StreamD
23+or StreamK modules. Once we do that fromStreamD/toStreamD get exposed and we
18− to rewrite transformations like `fromStreamK . toStreamK` to `id`. A plain
24+can apply rewrite rules to rewrite transformations like `fromStreamK .
19− `INLINE` pragma is usually enough to achieve that.
25+toStreamK` to `id`. A plain `INLINE` pragma is usually enough on combinators in
26+`Streamly.Prelude`.
20
27
21```
28```
22{-# RULES "fromStreamK/toStreamK fusion"
29{-# RULES "fromStreamK/toStreamK fusion"
23 forall s. toStreamK (fromStreamK s) = s #-}
30 forall s. toStreamK (fromStreamK s) = s #-}
24```
31```
25
32
33+Also, we have to prevent fromStreamK and toStreamK themselves from inlining in
34+this phase so that rewrite rules can be applied on them, therefore, we annotate
35+these functions with `INLINE_LATE`.
36+
37+## Fallback Rules
38+
26−* In some cases, if the operation could not fuse we want to use a fallback
39+In some cases, if the operation could not fuse we want to use a fallback
27− rewrite rule in the next phase. For such cases we use the INLINE_EARLY phase
40+rewrite rule in the next phase. For such cases we use the INLINE_EARLY phase
28− for the first rewrite and the INLINE_NORMAL phase for the fallback rules.
41+for the first rewrite and the INLINE_NORMAL phase for the fallback rules.
29
42
30− The fallback rules make sure that if we could not fuse the direct style
43+The fallback rules make sure that if we could not fuse the direct style
31− operations then better use the CPS style operation, because unfused direct
44+operations then better use the CPS style operation, because unfused direct
32− style would have worse performance than the CPS style ops.
45+style would have worse performance than the CPS style ops.
33
46
34```
47```
35{-# INLINE_EARLY unfoldr #-}
48{-# INLINE_EARLY unfoldr #-}
@@ -39,37 +52,44 @@ unfoldr step seed = fromStreamS (S.unfoldr step seed)
39 forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}
52 forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}
40```
53```
41
54
42−* Assuming that `fromStreamK/toStreamK` have been removed in the
55+## High Level Operation Fusion
43− `INLINE_EARLY` phase, we can then apply the combinator fusion rules in the
44− `INLINE_NORMAL` phase. For example, we can fuse two `map` operations into a
45− single `map` operation. Note that without removing the
46− `fromStreamK/toStreamK` wrapped around combinators, combinator fusion may
47− not work.
48
56
57+Since each high level combinator in `Streamly.Prelude` is wrapped in
58+`fromStreamD/toStreamD` etc. the combinator fusion cannot work unless we have
59+removed those and exposed consecutive operations e.g. a `map` followed by
60+another `map`. Assuming that redundant `fromStreamK/toStreamK` have been
61+removed in the `INLINE_EARLY` phase, we can then apply the combinator fusion
62+rules in the `INLINE_NORMAL` phase. For example, we can fuse two `map`
63+operations into a single `map` operation. Note that now we have exposed the
64+`StreamD/StreamK` implementations of combinators and the rules would apply on
65+those.
66+
67+## Inlining Higher Order Functions
68+
49−* Note that partially applied functions cannot be inlined. So if we have a code
69+Note that partially applied functions cannot be inlined. So if we have a code
50− like this:
70+like this:
51
71
52```
72```
53concatMap1 src = runStream $ S.concatMap (S.replicate 3) src
73concatMap1 src = runStream $ S.concatMap (S.replicate 3) src
54```
74```
55
75
56− We want to ensure that `concatMap` gets inlined before `replicate` so that
76+We want to ensure that `concatMap` gets inlined before `replicate` so that
57− `replicate` becomes fully applied before it gets inlined. Currently ensuring
77+`replicate` becomes fully applied before it gets inlined. Currently ensuring
58− that both of them are inlined in the same phase (`INLINE_NORMAL`) seems to be
78+that both of them are inlined in the same phase (`INLINE_NORMAL`) seems to be
59− enough to achieve that. In general, we should try to ensure that higher order
79+enough to achieve that. In general, we should try to ensure that higher order
60− functions are inlined before or in the same phase as the functions they can
80+functions are inlined before or in the same phase as the functions they can
61− consume as arguments. This means `StreamD` combinators should not be marked
81+consume as arguments. This means `StreamD` combinators should not be marked
62− as `INLINE` or `INLINE_EARLY`, instead they should all be marked as
82+as `INLINE` or `INLINE_EARLY`, instead they should all be marked as
63− `INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`
83+`INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`
64− etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like
84+etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like
65− `Streamly.Memory.Array` should also follow the same rules.
85+`Streamly.Memory.Array` should also follow the same rules.
66
86
67−* The inlining of the `step` function (in case of `StreamD`) is annotated as
87+## Stream Fusion
68− `INLINE_LATE` so that those are inlined after fusion rules have been applied.
69
88
70−* The inlining of, `fromStreamK/toStreamK` also happens in the `INLINE_LATE`
89+In StreamD combinators, inlining the inner step or loop functions too early
71− phase because the INLINE_EARLY and INLINE_NORMAL phases require them
90+i.e. in the same pahse or before the outer function is inlined may block stream
72− uninlined for rewrite rules to work.
91+fusion opportunities. Therefore, the inner step functions and folding loops are
92+marked as INLINE_LATE.
73
93
74## Specialization
94## Specialization
75
95
@@ -87,3 +107,5 @@ on `i` to get it specialized:
87 x <- action
107 x <- action
88 return $ Yield x (i - 1)
108 return $ Yield x (i - 1)
89```
109```
110+
111+`-flate-specialise` also helps in this case.