Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9afcfcd
Report unfused only in live binders
harendra-kumar Jul 1, 2026
607af22
Add binding specific fusion inspection pragmas
harendra-kumar Jul 4, 2026
7d95531
Do not report unboxed data constructors
harendra-kumar Jul 7, 2026
b18781f
Simplify fusion check annotations
harendra-kumar Jul 7, 2026
761af8a
Add support for FuseTypes annotation
harendra-kumar Jul 7, 2026
56ec550
Add NoFuseTypes annotation support
harendra-kumar Jul 7, 2026
6e106db
Add support for ShowCoreSize annotation
harendra-kumar Jul 8, 2026
568aa8d
Add support for DumpCore annotation
harendra-kumar Jul 8, 2026
2cf2f82
Report the type in the case-match scrutiny report
harendra-kumar Jul 8, 2026
d688491
Show qualified type names
harendra-kumar Jul 8, 2026
bf0f2e9
Keep the inspection output brief by default
harendra-kumar Jul 8, 2026
6ea2f71
Rename ShowCoreSize to MaxCoreSize, limit based
harendra-kumar Jul 9, 2026
a490209
Rename Inspect type and its constructors
harendra-kumar Jul 9, 2026
2ca9cfa
Add support for InspectTypeClasses annotations
harendra-kumar Jul 9, 2026
35f0973
Allow at most one annotation of each type
harendra-kumar Jul 9, 2026
e201124
Fail the build on violations when werror is on
harendra-kumar Jul 9, 2026
32a3707
Use binding name as prefix in the report messages
harendra-kumar Jul 9, 2026
6fba1d3
Update description
harendra-kumar Jul 9, 2026
d2365cf
Add bounds on template-haskell
harendra-kumar Jul 9, 2026
5ef3fb6
Add cabal.project to use fusion-plugin-types from github
harendra-kumar Jul 9, 2026
31a8f24
Add stack extra dep
harendra-kumar Jul 9, 2026
a274ea6
Fix for ghc 9.0.2
harendra-kumar Jul 9, 2026
8da7ece
Fix for GHC 8.10.7
harendra-kumar Jul 9, 2026
ce7d4a7
Update changelog and documentation
harendra-kumar Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ jobs:
CABAL_OPTIONS: >-
CABAL_REINIT_CONFIG=y
CABAL_CHECK_RELAX=y
CABAL_PROJECT=cabal.project

MATRIX_OPTIONS: ${{ matrix.pack_options }}

Expand Down
1 change: 1 addition & 0 deletions .packcheck.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.github/workflows/haskell.yml
.gitignore
appveyor.yml
cabal.project
cabal.project.ghc-head
flake.lock
flake.nix
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## Unreleased

* Add the following annotations:
* `FuseTypes`: mark types fusible within a specific binding.
* `NoFuseTypes`: disable `Fuse` annotation within a specific binding.
* `InspectTypes`: Forbid or permit existence of given types within a
binding.
* `InspectTypeClasses`: Forbid or permit existence of given type
classes within a binding.
* `MaxCoreSize`: report the size of the optimized Core of a binding.
* `DumpCore`: write the core of a specific binding to a file.

## 0.2.8

* Support ghc-9.14, adapt to HPT rule creation API change
Expand Down
182 changes: 178 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,185 @@ 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.

## Using the plugin

This plugin was primarily motivated by
[streamly](https://github.com/composewell/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:
## Using the plugin

There are two different stages where the plugin functionality is used, (1) when
writing code for fusion we annotate data types or bindings using annotations
imported from the `fusion-plugin-types` packages, (2) when compiling the code
we use the `fusion-plugin` package as a compiler plugin to make those
annotations work towards fusing the code better.

## Annotations to Enable Fusion

See the `fusion-plugin-types` package for detailed reference on available
annotations.

### Enabling Fusion of a Data Type

Library authors annotate the data types used in the intermediate states of
stream pipelines using the `Fuse` annotation. All the functions where these
data types appear are liable to be force inlined by the plugin to make the
types fuse.

### Enabling Fusion within a Function

`Fuse` marks a type as fusible everywhere it is used. Sometimes a type should
drive fusion only inside one particular function and must not force inlining
wherever else it happens to be used. For that, annotate the *binding* (not the
type) with `FuseTypes` from `Fusion.Plugin.Types`. The listed types then behave
exactly as if they carried a `Fuse` annotation, but only while inlining inside
that one binding:

```haskell
{-# LANGUAGE TemplateHaskellQuotes #-}

import Fusion.Plugin.Types (FuseTypes(..))

{-# ANN myFunction (FuseTypes [''Step, ''MyMaybe]) #-}
myFunction :: ...
```

Type references are TH `Name`s (`''Step`), so a typo or a stale reference to a
renamed type is a compile error rather than a silently-ignored annotation.
Using `''Foo` requires `{-# LANGUAGE TemplateHaskellQuotes #-}` (or the heavier
`TemplateHaskell`) in the annotated module.

### Disabling Fusion within a Function

`NoFuseTypes` is the inverse of `Fuse`/`FuseTypes`. Sometimes a type should
drive fusion in general (via a module-wide `Fuse` annotation) but must *not*
force inlining inside one particular function. Annotate that *binding* with
`NoFuseTypes` from `Fusion.Plugin.Types`: the listed types then behave as if
they carried no `Fuse` annotation, disabling the forced inlining they would
otherwise drive, but only while inlining inside that one binding. Fusion of
those types everywhere else in the module is unaffected:

```haskell
{-# LANGUAGE TemplateHaskellQuotes #-}

import Fusion.Plugin.Types (NoFuseTypes(..))

{-# ANN myFunction (NoFuseTypes [''Step, ''MyMaybe]) #-}
myFunction :: ...
```

## Annotations to Verify Fusion

### Inspecting Types within a Function

To verify elimination of certain types within a function annotate the
function with `InspectTypes`. If a violation is found the plugin will complain
providing details of the violation. When `werror` plugin options is used it
will fail the compilation on violation.

```haskell
{-# LANGUAGE TemplateHaskellQuotes #-}

import Fusion.Plugin.Types (InspectTypes(..))
```

Complain if any `Fuse` annotated type is found in the function.
```haskell
{-# ANN function1 (ForbidFused [] []) #-}
function1 :: ...
```

Also forbid `Maybe` as well even though it isn't Fuse-annotated.
```haskell
{-# ANN function1a (ForbidFused [''Maybe] []) #-}
function1a :: ...
```

Disallow `Maybe`, but explicitly allow `Step` even though it is Fuse-annotated
we allow it to be present in this binding.
```haskell
{-# ANN function1b (ForbidFused [''Maybe] [''Step]) #-}
function1b :: ...
```

Disallow the specified types and allow the rest, irrespective of `Fuse`
annotation.
```haskell
{-# ANN function2 (ForbidTypes [''Step]) #-}
function2 :: ...
```

Allow only the specified types and disallow all others.
```haskell
{-# ANN function3 (PermitTypes [''Int, ''IO]) #-}
function3 :: ...
```

To show all boxed types used within a function:
```haskell
{-# ANN function4 (PermitTypes []) #-}
function4 :: ...
```

### Inspecting type class dictionaries

To check the presence or absence of type classes in the Core of a binding,
annotate it with `InspectTypeClasses`. A type class appears in Core as a
dictionary argument; a class that survives to Core usually means a dictionary
that failed to specialize away.

```haskell
{-# LANGUAGE TemplateHaskellQuotes #-}

import Fusion.Plugin.Types (InspectTypeClasses(..))
```

'Num' should not appear in the core.
```haskell
{-# ANN function1 (ForbidTypeClasses [''Num]) #-}
function1 :: ...
```

'Ord' and 'Eq' can appear but no other type class can be present.
```haskell
{-# ANN function2 (PermitTypeClasses [''Ord, ''Eq]) #-}
function2 :: ...
```

### Inspecting Core Size of a Function

Sometimes the Core blows up due to aggressive inlining and SpecConstr
optimizations. To guard against or detect such issues we can use the
`MaxCoreSize` as a core size ratchet for a function.

```haskell
import Fusion.Plugin.Types (MaxCoreSize(..))

{-# ANN myFunction (MaxCoreSize 1000) #-}
myFunction :: ...
```

This prints a line such as:
```
fusion-plugin: myFunction: core size (1361 terms) exceeds the specified size (1000 terms).
```

### Generating Core of a Function

Use `DumpCore` annotation to write the final simplified core of a function to a
file under `fusion-plugin-output` directory:
```haskell
{-# ANN myFunction DumpCore #-}
myFunction :: ...
```

You can examine the core to find the reported violations.

### Compilation

To make the fusion annotations do the actual work you need to compile
your code with the fusion-plugin added to GHC during compilation. To do
that add this package to the `build-depends` in the cabal file of the
package to be compiled and use the following ghc options:
`ghc-options: -O2 -fplugin=Fusion.Plugin`

### Plugin options
Expand All @@ -61,6 +232,9 @@ in a different file.
`-fplugin-opt=Fusion.Plugin:verbose=1`: report unfused functions. Verbosity
levels `2`, `3`, `4` can be used for more verbose output.

`-fplugin-opt=Fusion.Plugin:werror`: treat annotation-check violations as
errors instead of warnings.

## See also

If you are a library author looking to annotate the types, you need to
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ environment:
# cabal options
# ------------------------------------------------------------------------
CABAL_CHECK_RELAX: "y"
#CABAL_PROJECT: "cabal.project"
CABAL_PROJECT: "cabal.project"

# ------------------------------------------------------------------------
# Location of packcheck.sh (the shell script invoked to perform CI tests ).
Expand Down
6 changes: 6 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
packages: fusion-plugin.cabal

source-repository-package
type: git
location: https://github.com/composewell/fusion-plugin-types.git
tag: d4fa57623450ba4a43f08ebd7cfbfc2745035375
36 changes: 20 additions & 16 deletions fusion-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ name: fusion-plugin
version: 0.2.8
synopsis: GHC plugin to make stream fusion more predictable.
description:
This plugin provides the programmer with a way to annotate certain
types using a 'Fuse' pragma from the
This GHC plugin provides the programmer with a way to annotate types
and functions using various annotations from the
<https://hackage.haskell.org/package/fusion-plugin-types fusion-plugin-types>
package. The programmer would annotate the types that are to be
eliminated by fusion. 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.
package, via GHC's @ANN@ pragma. The annotations are used during
compilation to eliminate intermediate constructors by automatically
enforcing inlining within GHC where fusion is needed. The plugin also
provides annotations to verify and report that specific types are
actually eliminated from the function in the final generated Core. It
also provides debugging mechanisms to dump the Core for a specific
function or report if the Core size blows up due to aggressive
optimizations.
.
This plugin was primarily motivated by
<https://streamly.composewell.com streamly> but it can
Expand Down Expand Up @@ -49,16 +52,17 @@ source-repository head

library
exposed-modules: Fusion.Plugin
build-depends: base >= 4.0 && < 5
, containers >= 0.5.6.2 && < 0.9
, directory >= 1.2.2.0 && < 1.4
, filepath >= 1.4 && < 1.6
, ghc >= 7.10.3 && < 9.15
, syb >= 0.7 && < 0.8
, time >= 1.5 && < 1.16
, transformers >= 0.4 && < 0.7
build-depends: base >= 4.0 && < 5
, containers >= 0.5.6.2 && < 0.9
, directory >= 1.2.2.0 && < 1.4
, filepath >= 1.4 && < 1.6
, ghc >= 7.10.3 && < 9.15
, syb >= 0.7 && < 0.8
, template-haskell >= 2.16 && < 2.25
, time >= 1.5 && < 1.16
, transformers >= 0.4 && < 0.7

, fusion-plugin-types >= 0.1 && < 0.2
, fusion-plugin-types >= 0.1.1 && < 0.2
hs-source-dirs: src
ghc-options: -Wall
if impl(ghc >= 8.0)
Expand Down
Loading
Loading