@@ -10,26 +10,39 @@ i.e. phase 0, 1 and 2. We have defined them as follows in `inline.h`:
1010#define INLINE_LATE INLINE [0]
1111```
1212
13−* The combinators in `Streamly.Prelude` are defined in terms of combinators in
14− `Streamly.Streams.StreamD` (Direct style streams) or
15− `Streamly.Streams.StreamK` (CPS style streams). We convert the stream from
16− `StreamD` to `StreamK` representation or vice versa in some cases. In the
17− first inlining phase we expand these combinators and apply the rewrite rules
18− to rewrite transformations like `fromStreamK . toStreamK` to `id`. A plain
19− `INLINE` pragma is usually enough to achieve that.
13+## Low Level `fromStreamD/toStreamD` Fusion
14+
15+The combinators in `Streamly.Prelude` are defined in terms of combinators in
16+`Streamly.Streams.StreamD` (Direct style streams) or `Streamly.Streams.StreamK`
17+(CPS style streams). We convert the stream from `StreamD` to `StreamK`
18+representation or vice versa in some cases.
19+
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
24+can apply rewrite rules to rewrite transformations like `fromStreamK .
25+toStreamK` to `id`. A plain `INLINE` pragma is usually enough on combinators in
26+`Streamly.Prelude`.
2027
2128```
2229{-# RULES "fromStreamK/toStreamK fusion"
2330 forall s. toStreamK (fromStreamK s) = s #-}
2431```
2532
26−* 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
28− for the first rewrite and the INLINE_NORMAL phase for the fallback rules.
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+
39+In some cases, if the operation could not fuse we want to use a fallback
40+rewrite rule in the next phase. For such cases we use the INLINE_EARLY phase
41+for the first rewrite and the INLINE_NORMAL phase for the fallback rules.
2942
30− 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
32− style would have worse performance than the CPS style ops.
43+The fallback rules make sure that if we could not fuse the direct style
44+operations then better use the CPS style operation, because unfused direct
45+style would have worse performance than the CPS style ops.
3346
3447```
3548{-# INLINE_EARLY unfoldr #-}
@@ -39,37 +52,44 @@ unfoldr step seed = fromStreamS (S.unfoldr step seed)
3952 forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}
4053```
4154
42−* Assuming that `fromStreamK/toStreamK` have been removed in the
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.
55+## High Level Operation Fusion
4856
49−* Note that partially applied functions cannot be inlined. So if we have a code
50− like this:
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+
69+Note that partially applied functions cannot be inlined. So if we have a code
70+like this:
5171
5272```
5373concatMap1 src = runStream $ S.concatMap (S.replicate 3) src
5474```
5575
56− We want to ensure that `concatMap` gets inlined before `replicate` so that
57− `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
59− 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
61− consume as arguments. This means `StreamD` combinators should not be marked
62− as `INLINE` or `INLINE_EARLY`, instead they should all be marked as
63− `INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`
64− etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like
65− `Streamly.Memory.Array` should also follow the same rules.
76+We want to ensure that `concatMap` gets inlined before `replicate` so that
77+`replicate` becomes fully applied before it gets inlined. Currently ensuring
78+that both of them are inlined in the same phase (`INLINE_NORMAL`) seems to be
79+enough to achieve that. In general, we should try to ensure that higher order
80+functions are inlined before or in the same phase as the functions they can
81+consume as arguments. This means `StreamD` combinators should not be marked
82+as `INLINE` or `INLINE_EARLY`, instead they should all be marked as
83+`INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`
84+etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like
85+`Streamly.Memory.Array` should also follow the same rules.
6686
67−* The inlining of the `step` function (in case of `StreamD`) is annotated as
68− `INLINE_LATE` so that those are inlined after fusion rules have been applied.
87+## Stream Fusion
6988
70−* The inlining of, `fromStreamK/toStreamK` also happens in the `INLINE_LATE`
71− phase because the INLINE_EARLY and INLINE_NORMAL phases require them
72− uninlined for rewrite rules to work.
89+In StreamD combinators, inlining the inner step or loop functions too early
90+i.e. in the same pahse or before the outer function is inlined may block stream
91+fusion opportunities. Therefore, the inner step functions and folding loops are
92+marked as INLINE_LATE.
7393
7494## Specialization
7595
@@ -87,3 +107,5 @@ on `i` to get it specialized:
87107 x <- action
88108 return $ Yield x (i - 1)
89109```
110+
111+`-flate-specialise` also helps in this case.