Fusion.Plugin
Stream fusion depends on the GHC case-of-case transformations eliminating intermediate constructors. Case-of-case transformation in turn depends on inlining. During core-to-core transformations GHC may create several internal bindings (e.g. join points) which may not get inlined because their size is bigger than GHC's inlining threshold. Even though we know that after fusion the resulting code would be smaller and more efficient. The programmer cannot force inlining of these bindings as there is no way for the programmer to address these bindings at the source level because they are internal, generated during core-to-core transformations. As a result stream fusion fails unpredictably depending on whether GHC was able to inline the internal bindings or not.
See GHC ticket #17075 for more details.
This plugin provides the programmer with a way to annotate certain types
using a custom Fuse
annotation. The programmer would annotate the
types that are to be eliminated by fusion via case-of-case transformations.
During the simplifier phase the plugin goes through the relevant bindings
and if one of these types are found inside a binding then that binding is
marked to be inlined irrespective of the size.
At the right places, fusion can provide dramatic performance improvements (e.g. 10x) to the code.
Using the Plugin
This plugin was primarily motivated by fusion issues discovered in streamly but it can be used in general.
To use this plugin, add this package to your build-depends
and pass the following to your ghc-options:
ghc-options: -O2 -fplugin=Fusion.Plugin
The following currently works only for GHC versions less than 9.0.
To dump the core after each core to core transformation, pass the following to your ghc-options:
ghc-options: -O2 -fplugin=Fusion.Plugin -fplugin-opt=Fusion.Plugin:dump-core
Output from each transformation is then printed in a different file.
Implementation Details
The plugin runs after the simplifier phase 0. It finds all non recursive
join point bindings whose definition begins with a case match on a type that
is annotated with Fuse
. It then sets AlwaysInlinePragma on those
bindings. This is followed by two runs of a gentle simplify pass that does
both inlining and case-of-case. This is followed by the rest of CoreToDos.
Results
This plugin has been used extensively in the streaming library streamly. Several file IO benchmarks have shown 2x-6x improvements. With the use of this plugin stream fusion in streamly has become much more predictable which has been verified by inspecting the core generated by GHC and by inspection testing for the presence of the stream state constructors.