@@ -0,0 +1,89 @@
1+## INLINE Phases
2+
3+A missing inline or inline in an incorrect GHC simplifier phase can adversely
4+impact performance. We use three builtin phases of GHC simplifier for inlining
5+i.e. phase 0, 1 and 2. We have defined them as follows in `inline.h`:
6+
7+```
8+#define INLINE_EARLY INLINE [2]
9+#define INLINE_NORMAL INLINE [1]
10+#define INLINE_LATE INLINE [0]
11+```
12+
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.
20+
21+```
22+{-# RULES "fromStreamK/toStreamK fusion"
23+ forall s. toStreamK (fromStreamK s) = s #-}
24+```
25+
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.
29+
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.
33+
34+```
35+{-# INLINE_EARLY unfoldr #-}
36+unfoldr :: (Monad m, IsStream t) => (b -> Maybe (a, b)) -> b -> t m a
37+unfoldr step seed = fromStreamS (S.unfoldr step seed)
38+{-# RULES "unfoldr fallback to StreamK" [1]
39+ forall a b. S.toStreamK (S.unfoldr a b) = K.unfoldr a b #-}
40+```
41+
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.
48+
49+* Note that partially applied functions cannot be inlined. So if we have a code
50+ like this:
51+
52+```
53+concatMap1 src = runStream $ S.concatMap (S.replicate 3) src
54+```
55+
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.Mem.Array` should also follow the same rules.
66+
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.
69+
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.
73+
74+## Specialization
75+
76+In some cases, the `step` function in `StreamD` does not get specialized when
77+inlined unless it is provided with an explicit signature or made a lambda, for
78+example, in the `replicate/replicateM` combinator we need the type annotation
79+on `i` to get it specialized:
80+
81+```
82+ {-# INLINE_LATE step #-}
83+ step _ (i :: Int) =
84+ if i <= 0
85+ then return Stop
86+ else do
87+ x <- action
88+ return $ Yield x (i - 1)
89+```