core-of-name (empty) → 0.1.0.0
raw patch · 8 files changed
+323/−0 lines, 8 filesdep +basedep +core-of-namedep +ghcsetup-changed
Dependencies added: base, core-of-name, ghc, template-haskell
Files
- CHANGELOG.md +11/−0
- LICENSE +26/−0
- README.md +71/−0
- Setup.hs +2/−0
- app/Main.hs +23/−0
- core-of-name.cabal +47/−0
- src/CoreOfName/Plugin.hs +93/−0
- src/CoreOfName/Types.hs +50/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for `core-of-name`++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## Unreleased++## 0.1.0.0 - YYYY-MM-DD
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright 2026 Author name here++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,71 @@+# core-of-name++[](https://github.com/ocramz/ghc-plugin-core-of-name/actions/workflows/ci.yml)++GHC plugin that prints the Core intermediate representation of annotated Haskell bindings during compilation.++Based on the technique described in+[Finding the Core of an expression using Template Haskell and a custom GHC Core plugin](https://ocramz.github.io/posts/2021-06-22-finding-core-th.html), which in turn was inspired by [`inspection-testing`](https://hackage.haskell.org/package/inspection-testing)++## Usage++In the module whose bindings you want to inspect, enable `TemplateHaskell` and the plugin, then call `coreOf` or `coreOfWith` after the binding:++### `coreOf` — print Core to stdout++```haskell+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin=CoreOfName.Plugin #-}+module MyModule where++import CoreOfName.Types (coreOf)++f :: Double -> Double -> Double+f = \x y -> sqrt x + y++coreOf 'f+```++The Core representation of `f` will be printed interleaved with the normal compiler output during `stack build` / `cabal build`.++### `coreOfWith` — write Core to a file++Use `coreOfWith` to direct the output to a file instead of stdout with this shorthand:++```haskell+{-# LANGUAGE TemplateHaskell #-}+{-# language OverloadedStrings #-}+{-# OPTIONS_GHC -fplugin=CoreOfName.Plugin #-}+module MyModule where++import CoreOfName.Types (coreOfWith)++f :: Double -> Double -> Double+f = \x y -> sqrt x + y++coreOfWith "output.core" 'f+```++NB if two or more declarations use the same output file, the file will be _overwritten_. It is best to assign one Core output file per function.++The string literal is an `Options` value via the `IsString` instance and is equivalent to `OToFile "output.core"`.++### Explicit `Options`++You can also pass `Options` values directly:++```haskell+import CoreOfName.Types (coreOfWith, Options(..))++-- print to stdout (same as coreOf)+coreOfWith OPrintCore 'f++-- write to a file+coreOfWith (OToFile "output.core") 'f+```++## Building++```+stack clean && stack build+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE TemplateHaskell #-}+{-# language OverloadedStrings #-}+{-# OPTIONS_GHC -fplugin=CoreOfName.Plugin #-}+module Main (main) where++import CoreOfName.Types (coreOf, coreOfWith, Options(..))++g :: Double -> Double+g = \x -> x + 1+-- {-# NOINLINE g #-}++coreOfWith "g.core" 'g++-- Try building with either type signature for different Core output:+f :: Double -> Double -> Double -- gives unboxed primops+-- f :: Floating a => a -> a -> a -- gives dictionary-passing style+f = \x y -> g x + y++coreOfWith "f.core" 'f+-- coreOf 'f++main :: IO ()+main = putStrLn "Plugin ran at compile time — see build output for Core of 'f'"
+ core-of-name.cabal view
@@ -0,0 +1,47 @@+cabal-version: 2.2++name: core-of-name+version: 0.1.0.0+synopsis: Print the Core representation of a binding with a GHC plugin.+description: GHC plugin that prints the Core representation of a given binding, either to the console or to a file. Useful for debugging and learning about GHC's Core language.+homepage: https://github.com/ocramz/ghc-plugin-core-of-name+license: BSD-3-Clause+license-file: LICENSE+author: Marco Zocca+maintainer: ocramz+copyright: 2026 Marco Zocca+category: Development+build-type: Simple+tested-with: GHC == 9.10.3+extra-source-files: README.md+extra-doc-files: CHANGELOG.md++library+ default-language: Haskell2010+ hs-source-dirs: src+ exposed-modules: CoreOfName.Types+ , CoreOfName.Plugin+ build-depends: base >= 4.7 && < 5+ , ghc >= 9.8 && < 9.14+ , template-haskell >= 2.15 && < 2.23+ ghc-options: -Wall+ -Wcompat+ -Widentities+ -Wincomplete-record-updates+ -Wincomplete-uni-patterns+ -Wmissing-home-modules+ -Wpartial-fields+ -Wredundant-constraints++executable core-of-name+ default-language: Haskell2010+ hs-source-dirs: app+ main-is: Main.hs+ build-depends: base+ , core-of-name+ ghc-options: -Wall+++source-repository head+ type: git+ location: https://github.com/ocramz/ghc-plugin-core-of-name
+ src/CoreOfName/Plugin.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE LambdaCase #-}+module CoreOfName.Plugin+ ( plugin+ ) where++import Control.Monad.IO.Class (liftIO)+import Data.Foldable (traverse_)++import GHC.Plugins+ ( Plugin(..), defaultPlugin, CommandLineOption+ , fromSerialized, deserializeWithData+ , ModGuts(..), Name, CoreExpr, Var+ , flattenBinds, getName, thNameToGhcName+ , PluginRecompile(..)+ )+import GHC.Core.Opt.Monad (CoreM, putMsgS, getDynFlags)+import GHC.Core.Opt.Pipeline.Types (CoreToDo(..))+import GHC.Driver.Ppr (showSDoc)+import GHC.Types.Annotations (Annotation(..))+import GHC.Utils.Outputable (ppr)+import qualified Language.Haskell.TH.Syntax as TH (Name)++import CoreOfName.Types (Target(..), Options(..))++-- | The GHC plugin entry point. Appends a Core-to-Core pass that+-- finds annotated bindings and prints their Core representation.+plugin :: Plugin+plugin = defaultPlugin+ { installCoreToDos = install+ , pluginRecompile = \_ -> pure NoForceRecompile+ }++-- | Append our plugin pass at the end of the Core pipeline+install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]+install cliOpts todos = pure (todos ++ [pass])+ where+ pass = CoreDoPluginPass "CoreOfName" $ \guts -> do+ let (guts', targets) = extractTargets guts+ liftIO $ putStrLn $ unwords ["CLI options:", show cliOpts, "| Found", show (length targets), "targets"]+ traverse_ (printCore guts') targets+ pure guts'+++-- | Pretty-print the Core representation of the binding with the+-- given TH 'Name'.+printCore :: ModGuts -> Target -> CoreM ()+printCore guts target = do+ let + thn = tgName target+ thOpts = tgOptions target+ mn <- lookupTHName guts thn+ case mn of+ Just (_, coreExpr) -> do+ dflags <- getDynFlags+ case thOpts of+ OPrintCore -> putMsgS $ showSDoc dflags (ppr coreExpr)+ OToFile fp -> liftIO $ writeFile fp (showSDoc dflags (ppr coreExpr))+ Nothing ->+ putMsgS $ "CoreOfName: Cannot find name " ++ show thn++-- | Resolve a TH 'Name' to a GHC 'Name' and look it up in the+-- module's Core bindings.+lookupTHName :: ModGuts -> TH.Name -> CoreM (Maybe (Var, CoreExpr))+lookupTHName guts thn = thNameToGhcName thn >>= \case+ Nothing -> do+ putMsgS $ "CoreOfName: Could not resolve TH name " ++ show thn+ pure Nothing+ Just n -> pure $ lookupNameInGuts n+ where+ lookupNameInGuts :: Name -> Maybe (Var, CoreExpr)+ lookupNameInGuts n =+ case [ (v, e) | (v, e) <- flattenBinds (mg_binds guts), getName v == n ] of+ (hit:_) -> Just hit+ [] -> Nothing++-- | Decode 'Target' annotations from the module guts, returning+-- cleaned guts (annotations consumed) and the list of targets.+extractTargets :: ModGuts -> (ModGuts, [Target])+extractTargets guts = (guts', targets)+ where+ (anns_clean, targets) = partitionMaybe findTargetAnn (mg_anns guts)+ guts' = guts { mg_anns = anns_clean }+ findTargetAnn (Annotation _ payload) =+ fromSerialized deserializeWithData payload++-- | Partition a list using a function that returns 'Just' for+-- elements to collect and 'Nothing' for elements to keep.+partitionMaybe :: (a -> Maybe b) -> [a] -> ([a], [b])+partitionMaybe _ [] = ([], [])+partitionMaybe f (x:xs) = case f x of+ Nothing -> (x : as, bs)+ Just b -> (as, b : bs)+ where (as, bs) = partitionMaybe f xs
+ src/CoreOfName/Types.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE DeriveDataTypeable #-}+module CoreOfName.Types+ ( Target(..)+ , coreOf+ , Options(..)+ , defaultOptions+ , coreOfWith+ ) where++import Data.Data (Data)+import Data.String (IsString(..))+import Language.Haskell.TH (Name, AnnTarget(..), Pragma(..), Dec(..), Q)+import Language.Haskell.TH.Syntax (liftData)++-- | Annotation payload carrying the TH 'Name' of a binding whose+-- GHC Core representation we want to inspect.+data Target = MkTarget { + tgOptions :: Options+, tgName :: Name+} deriving (Data)++data Options = OPrintCore + | OToFile FilePath + deriving (Data)+instance IsString Options where+ fromString s = OToFile s++defaultOptions :: Options+defaultOptions = OPrintCore++-- | Template Haskell splice that attaches a module-level annotation+-- carrying the given 'Name'. Usage (in client module):+--+-- @+-- {-\# LANGUAGE TemplateHaskell \#-}+-- f :: Double -> Double -> Double+-- f = \\x y -> sqrt x + y+--+-- coreOf \'f+-- @+coreOf :: Name -> Q [Dec]+coreOf = coreOfWith defaultOptions++coreOfWith :: Options -> Name -> Q [Dec]+coreOfWith opts n = do+ annExpr <- liftData (MkTarget opts n)+ pure [PragmaD (AnnP ModuleAnnotation annExpr)]+++