Skip to content

Commit

Permalink
Add a DynFlags plugin.
Browse files Browse the repository at this point in the history
This only has an effect on GHC 8.10 and later. Older versions need to manually
set the relevant flags.

TODO: warn when we had to override flags set by the user (ideally, we'd only
warn when they explicitly differ, not when we're overriding behavior set by
`-O2` or something.

This also currently doesn't work, because some of our tests break when we don't
ignore interface pragmas (I was hoping this would go away when we removed the
presimplifier, but it didn't).

Fixes #43.
  • Loading branch information
sellout committed Feb 9, 2024
1 parent 69516c0 commit 340f62e
Show file tree
Hide file tree
Showing 17 changed files with 73 additions and 52 deletions.
33 changes: 33 additions & 0 deletions ghc/Categorifier/GHC/Driver.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# OPTIONS_GHC -Wno-partial-type-signatures #-}

module Categorifier.GHC.Driver
( module DynFlags,
module HscTypes,
module Outputable,
module Plugins,
defaultPurePlugin,
pureDynflagsAndCorePlugin,
)
where

Expand All @@ -28,3 +32,32 @@ import HscTypes hiding (InteractiveContext (..), InteractiveImport (..), ModGuts
import Outputable hiding (Outputable (..), renderWithStyle)
import Plugins
#endif

-- | Like `defaultPlugin`, but specifies that the plugin is pure (when GHC allows).
defaultPurePlugin :: Plugin
#if MIN_VERSION_ghc(8, 6, 0)
defaultPurePlugin = defaultPlugin {pluginRecompile = purePlugin}
#else
defaultPurePlugin = defaultPlugin
#endif

-- | Builds a pure plugin that has both `DynFlags` and `Core` components. Prior to GHC 8.10, the
-- `DynFlags` component will be ignored and so the flags must be manually configured to match.
pureDynflagsAndCorePlugin ::
([CommandLineOption] -> DynFlags -> IO DynFlags) ->
-- | @([CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo])@, but skipped to avoid import issues
_ ->
Plugin
#if MIN_VERSION_ghc(9, 2, 0)
pureDynflagsAndCorePlugin dynflags core =
defaultPurePlugin
{ driverPlugin =
\opts hsc -> (\newFlags -> hsc {hsc_dflags = newFlags}) <$> dynflags opts (hsc_dflags hsc),
installCoreToDos = core
}
#elif MIN_VERSION_ghc(8, 10, 0)
pureDynflagsAndCorePlugin dynflags core =
defaultPurePlugin {dynflagsPlugin = dynflags, installCoreToDos = core}
#else
pureDynflagsAndCorePlugin _ core = defaultPurePlugin {installCoreToDos = core}
#endif
1 change: 0 additions & 1 deletion ghc/categorifier-ghc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ library
Paths_categorifier_ghc
ghc-options:
-O2
-fignore-interface-pragmas
build-depends:
, PyF ^>=0.9.0 || ^>=0.10.0 || ^>=0.11.0
, bytestring ^>=0.10.9 || ^>=0.11.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,3 @@ test-suite adjunctions-hierarchy-optimized
main-is: Adjunctions/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,3 @@ test-suite categories-hierarchy-optimized
main-is: Categories/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,3 @@ test-suite concat-extensions-hierarchy-optimized
main-is: ConCatExtensions/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ test-suite concat-class-hierarchy-optimized
main-is: ConCat/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas

test-suite concat-function-hierarchy
import: hierarchy-tests
Expand All @@ -104,4 +103,3 @@ test-suite concat-function-hierarchy-optimized
main-is: ConCat/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,3 @@ test-suite ghc-bignum-hierarchy-optimized
main-is: GhcBignum/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,3 @@ test-suite linear-base-hierarchy-optimized
main-is: LinearBase/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,3 @@ test-suite unconcat-hierarchy-optimized
main-is: UnconCat/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,3 @@ test-suite vec-hierarchy-optimized
main-is: Vec/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
1 change: 0 additions & 1 deletion plugin-test/categorifier-plugin-test.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,3 @@ test-suite base-hierarchy-optimized
main-is: Base/Main.hs
ghc-options:
-O2
-fignore-interface-pragmas
18 changes: 9 additions & 9 deletions plugin/Categorifier.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ where
import Categorifier.CommandLineOptions (OptionGroup, partitionOptions)
import Categorifier.Common.IO.Exception (throwIOAsException)
import qualified Categorifier.Core
import qualified Categorifier.DynFlags
import qualified Categorifier.GHC.Core as GhcPlugins
import qualified Categorifier.GHC.Driver as GhcPlugins
import Control.Applicative (liftA2)
Expand All @@ -30,15 +31,14 @@ import PyF (fmt)
-- for more information.
plugin :: GhcPlugins.Plugin
plugin =
GhcPlugins.defaultPlugin
{ GhcPlugins.installCoreToDos =
\opts ->
join
. GhcPlugins.liftIO
. liftA2 Categorifier.Core.install (partitionOptions' opts)
. pure,
GhcPlugins.pluginRecompile = GhcPlugins.flagRecompile
}
GhcPlugins.pureDynflagsAndCorePlugin
(\_opts -> pure . Categorifier.DynFlags.plugin)
( \opts ->
join
. GhcPlugins.liftIO
. liftA2 Categorifier.Core.install (partitionOptions' opts)
. pure
)

partitionOptions' :: [GhcPlugins.CommandLineOption] -> IO (Map OptionGroup [Text])
partitionOptions' opts =
Expand Down
8 changes: 3 additions & 5 deletions plugin/Categorifier/Core/ErrorHandling.hs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,9 @@ required by {showE expr}.|]
`inlinable` pragma to the definition or compiling with
`-fexpose-all-unfoldings` to make _every_ operation inlinable. It's also
important that the module containing the call to `categorify` is compiled
with `-fno-ignore-interface-pragmas` (also implied by `-O`). If the
unfolding that's missing is for `$j` (GHC-internal join points), you may
need to bump `-funfolding-creation-threshold` on the modules you're
depending on. If there is still no unfolding available, please file an issue
against the plugin.|]
with `-fno-ignore-interface-pragmas` (also implied by `-O` and enabled
automatically on GHC 8.10.1 or later). If there is still no unfolding
available, please file an issue against the plugin.|]
Plugins.BootUnfolding ->
[fmt|
The identifier is defined in an hi-boot file, so can't be inlined.|]
Expand Down
20 changes: 20 additions & 0 deletions plugin/Categorifier/DynFlags.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Categorifier.DynFlags
( plugin,
)
where

import qualified Categorifier.GHC.Driver as GHC

plugin :: GHC.DynFlags -> GHC.DynFlags
plugin = setUpDynFlags

-- | This sets up flags that allow the plugin to do what it needs to.
--
-- For a compiler that doesn't support this plugin (before GHC 8.10), the following GHC options
-- approximate it:
--
-- [@-fno-ignore-interface-pragmas@]: Ensures we can inline definitions that we don't know how to
-- interpret directly to the target category. This flag is also
-- implied by @-O@ and @-O2@.
setUpDynFlags :: GHC.DynFlags -> GHC.DynFlags
setUpDynFlags = GHC.unSetGeneralFlag' GHC.Opt_IgnoreInterfacePragmas
32 changes: 7 additions & 25 deletions plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ directly, as well as ones that are depended on (transitively) by the ones that u

### targets you want to use `categorify` in

- enable the plugin with `-fplugin=Categorifier`,
- ensure inlining is available with `-fno-ignore-interface-pragmas` (implied by `-O` or `-O2`), and
- enable the plugin with `-fplugin=Categorifier`;
- if you are using a version of GHC older than 8.10.1, use the flags described in
[Categorifier.DynFlags](./Categorifier/DynFlags.hs); and
- import `Categorifier.Categorify` to make `categorify` available (you must import the _entire_ module,
but it may be qualified).

Expand All @@ -32,26 +33,6 @@ directly, as well as ones that are depended on (transitively) by the ones that u
- define `Categorifier.HasRep` instances for any types that you use in a converted function (the plugin
will tell you if you are missing any when you try to convert)

### fine-tuning inlining sizes

It can be difficult to find a reasonable setting for the various inlining thresholds. This attempts
to lay out an approach for identifying one.

There are two significant GHC flags for adjusting inlining, `-funfolding-creation-threshold` and
`-funfolding-use-threshold`. They allow you to set an upper bound on the "size" of unfoldings that
will be considered for inlining.

1. set the `creation` (globally) threshold high, say `10000`;
2. test to see if the inlining issue goes away (if so, skip to step 5);
3. set the `use` (in `categorify` modules) threshold to match the `creation` threshold;
4. do a binary search on the `use` thresholds to minimize them as much as possible;
5. do a binary search on the `creation` thresholds to minimize them as much as possible (the lower
bound here is probably the minimum of 750 (the default) and the `use` threshold).

If either if these values is too small, you'll end up with errors complaining that some definition
couldn't be inlined. If they're too big, you'll get errors about "simplifier ticks exhausted" (in
which case, you can bump `-fsimpl-tick-factor`) and things will take a lot longer to compile.

### defining `HasRep` instances

You should use `Categorifier.Client.deriveHasRep` for all `HasRep` instances. However, for
Expand Down Expand Up @@ -146,9 +127,10 @@ This is ostensibly a more correct approach, given the way GHC is structured, but

There are a bunch of modules, this calls out the most important ones when diving in.

- [`Categorifier`](./Categorifier.hs) - this is the entrypoint of the plugin, everything that hooks into
GHC starts from here;
- [`Categorifier.Core.Categorify`](./Categorifier/Core/Categorify.hs) - the high-level logic of the
- [Categorifier](./Categorifier.hs) - this is the entrypoint of the plugin, everything that hooks into
GHC starts from here, with there being three immediate subcomponents: command-line option
handling, `DynFlags` settings, and the Core pass;
- [Categorifier.Core.Categorify](./Categorifier/Core/Categorify.hs) - the high-level logic of the
categorical transformation as described in Conal's paper, it tries to define as clearly as
possible the mapping from **Hask** to abstract categories;
- [`Categorifier.Hierarchy`](./Categorifier/Hierarchy.hs) - the mappings from abstract
Expand Down
2 changes: 1 addition & 1 deletion plugin/categorifier-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ library
-- __TODO__: move ...PrimOp to other-modules
Categorifier.Core.PrimOp
Categorifier.Core.Types
Categorifier.DynFlags
Categorifier.Hierarchy
other-modules:
Categorifier.Benchmark
Expand All @@ -67,7 +68,6 @@ library
Paths_categorifier_plugin
ghc-options:
-O2
-fignore-interface-pragmas
build-depends:
, PyF ^>=0.9.0 || ^>=0.10.0 || ^>=0.11.0
, barbies ^>=2.0.1
Expand Down
1 change: 0 additions & 1 deletion th/categorifier-th.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ library
Paths_categorifier_th
ghc-options:
-O2
-fignore-interface-pragmas
build-depends:
, PyF ^>=0.9.0 || ^>=0.10.0 || ^>=0.11.0
, categorifier-common
Expand Down

0 comments on commit 340f62e

Please sign in to comment.