From e259bfc66e25daa813f7a16f2499a3acb4e44e13 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Mon, 13 Jul 2026 18:53:53 +0530 Subject: [PATCH 1/5] Add annotations for pattern matches and allocations --- src/Fusion/Plugin/Types.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Fusion/Plugin/Types.hs b/src/Fusion/Plugin/Types.hs index 41770e0..c51ed9f 100644 --- a/src/Fusion/Plugin/Types.hs +++ b/src/Fusion/Plugin/Types.hs @@ -116,6 +116,8 @@ newtype NoFuseTypes = NoFuseTypes [Name] -- {-\# ANN function1b (ForbidFused [''Maybe] [''Step]) #-} -- {-\# ANN function2 (ForbidTypes [''Maybe]) #-} -- {-\# ANN function3 (PermitTypes [''Int, ''IO]) #-} +-- {-\# ANN function4 (PermitPatternMatches [''Int, ''IO]) #-} +-- {-\# ANN function5 (PermitAllocations [''Int, ''IO]) #-} -- @ data InspectTypes = ForbidFused [Name] [Name] @@ -132,6 +134,16 @@ data InspectTypes -- found in the binding -- a general "boxing detector", not limited to -- 'Fuse'-annotated types -- except the named types, which may appear -- freely. + | PermitPatternMatches [Name] + -- ^ Like 'PermitTypes' but only reports occurrences in a scrutinizing + -- (pattern-match, i.e. @case@) position -- a value being deconstructed -- + -- ignoring constructing positions. Reports every type pattern-matched in + -- the binding except the named types, which may appear freely. + | PermitAllocations [Name] + -- ^ Like 'PermitTypes' but only reports occurrences in a constructing + -- (allocating) position -- a value being built -- ignoring scrutinizing + -- positions. Reports every type constructed in the binding except the + -- named types, which may appear freely. deriving (Eq, Data) -- | A GHC annotation attached to a specific top level binding (via an @ANN@ From 6a890e40fb01b610d86cc6d5dcac26c3c61e40b7 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Mon, 13 Jul 2026 19:28:58 +0530 Subject: [PATCH 2/5] Rename Forbid/PermitTypes to Forbid/PermitBoxedUse --- src/Fusion/Plugin/Types.hs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Fusion/Plugin/Types.hs b/src/Fusion/Plugin/Types.hs index c51ed9f..8a93a53 100644 --- a/src/Fusion/Plugin/Types.hs +++ b/src/Fusion/Plugin/Types.hs @@ -114,8 +114,8 @@ newtype NoFuseTypes = NoFuseTypes [Name] -- {-\# ANN function1 (ForbidFused [] []) #-} -- {-\# ANN function1a (ForbidFused [''Maybe] []) #-} -- {-\# ANN function1b (ForbidFused [''Maybe] [''Step]) #-} --- {-\# ANN function2 (ForbidTypes [''Maybe]) #-} --- {-\# ANN function3 (PermitTypes [''Int, ''IO]) #-} +-- {-\# ANN function2 (ForbidBoxedUse [''Maybe]) #-} +-- {-\# ANN function3 (PermitBoxedUse [''Int, ''IO]) #-} -- {-\# ANN function4 (PermitPatternMatches [''Int, ''IO]) #-} -- {-\# ANN function5 (PermitAllocations [''Int, ''IO]) #-} -- @ @@ -125,22 +125,22 @@ data InspectTypes -- -- plus any types named in the first (forbid) list, minus any types -- named in the second (allow) list. A name present in both lists is -- allowed. - | ForbidTypes [Name] + | ForbidBoxedUse [Name] -- ^ Blocklist: report occurrences of exactly the named types/constructors -- found anywhere in the binding, regardless of whether they carry a 'Fuse' -- annotation. Everything else in core is fine. - | PermitTypes [Name] + | PermitBoxedUse [Name] -- ^ Allowlist: report occurrences of literally every type/constructor -- found in the binding -- a general "boxing detector", not limited to -- 'Fuse'-annotated types -- except the named types, which may appear -- freely. | PermitPatternMatches [Name] - -- ^ Like 'PermitTypes' but only reports occurrences in a scrutinizing + -- ^ Like 'PermitBoxedUse' but only reports occurrences in a scrutinizing -- (pattern-match, i.e. @case@) position -- a value being deconstructed -- -- ignoring constructing positions. Reports every type pattern-matched in -- the binding except the named types, which may appear freely. | PermitAllocations [Name] - -- ^ Like 'PermitTypes' but only reports occurrences in a constructing + -- ^ Like 'PermitBoxedUse' but only reports occurrences in a constructing -- (allocating) position -- a value being built -- ignoring scrutinizing -- positions. Reports every type constructed in the binding except the -- named types, which may appear freely. From 6080afef407463e3b05dc5abc780850c255cb86e Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 14 Jul 2026 14:53:26 +0530 Subject: [PATCH 3/5] Add a NoFuse annotation --- CHANGELOG.md | 4 +++- src/Fusion/Plugin/Types.hs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb9726..e448bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ within a binding. * Add `FuseTypes` annotation for making types locally fusible within a specific binding. -* Add `NoFuseTypes` annotation for overriding `Fuse` data type annotation +* Add `NoFuse` annotation for disabling any `Fuse` data type annotation inside a binding. +* Add `NoFuseTypes` annotation for disabling `Fuse` annotation for specific + types within a binding. * Add `MaxCoreSize` annotation to report if core size of a binding exceeds a certain threashold. diff --git a/src/Fusion/Plugin/Types.hs b/src/Fusion/Plugin/Types.hs index 8a93a53..44b6e01 100644 --- a/src/Fusion/Plugin/Types.hs +++ b/src/Fusion/Plugin/Types.hs @@ -26,6 +26,7 @@ module Fusion.Plugin.Types Fuse(..) , FuseTypes(..) , NoFuseTypes(..) + , NoFuse(..) -- ** Inspection Annotations -- | Annotations to find fusion violations. @@ -83,7 +84,8 @@ newtype FuseTypes = FuseTypes [Name] -- | A GHC annotation attached to a specific top level binding (via an @ANN@ -- pragma on the binding, not on a type) that makes each of the listed types -- behave as if it were /not/ annotated with 'Fuse', but /only/ for the purpose --- of inlining within that one binding -- not anywhere else in the module. +-- of inlining within that one binding -- not anywhere else in the module. This +-- is applicable for any nested bindings within the scope of that binding. -- -- This is the local override of 'Fuse' (and of 'FuseTypes'). Whereas @{-\# ANN -- type Step Fuse #-}@ marks @Step@ as fusible everywhere it is used, @{-\# ANN @@ -100,6 +102,17 @@ newtype FuseTypes = FuseTypes [Name] newtype NoFuseTypes = NoFuseTypes [Name] deriving (Eq, Data) +-- | A GHC annotation attached to a specific top level binding (via an @ANN@ +-- pragma on the binding, not on a type) that disables forced inlining of that +-- binding and any nested bindings within the scope of that binding altogether, +-- regardless of which types are involved. +-- +-- @ +-- {-\# ANN myFunc NoFuse #-} +-- @ +data NoFuse = NoFuse + deriving (Eq, Data) + -- | A GHC annotation attached to a specific top level binding (via an @ANN@ -- pragma on the binding, not on a type) that requests a fusion report for just -- that binding. From d4446dc9fcfc9bfa5f4bea86308890eca7dfbac8 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 14 Jul 2026 19:29:23 +0530 Subject: [PATCH 4/5] Add DumpCorePasses annotation --- CHANGELOG.md | 4 ++++ src/Fusion/Plugin/Types.hs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e448bbe..3632e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ types within a binding. * Add `MaxCoreSize` annotation to report if core size of a binding exceeds a certain threashold. +* Add `DumpCore` annotation to write the optimized core of a binding to a + file. +* Add `DumpCorePasses` annotation to write the core of a binding to a file + after every Core-to-core pass. ## 0.1.0 diff --git a/src/Fusion/Plugin/Types.hs b/src/Fusion/Plugin/Types.hs index 44b6e01..011e1aa 100644 --- a/src/Fusion/Plugin/Types.hs +++ b/src/Fusion/Plugin/Types.hs @@ -36,6 +36,7 @@ module Fusion.Plugin.Types -- ** Debugging Annotations , DumpCore(..) + , DumpCorePasses(..) ) where @@ -223,3 +224,33 @@ newtype MaxCoreSize = MaxCoreSize Int -- @ data DumpCore = DumpCore deriving (Eq, Data) + +-- | A GHC annotation attached to a specific top level binding (via an @ANN@ +-- pragma on the binding) that makes the plugin dump the Core of that binding +-- /after every Core-to-core pass/, each to its own file. This is the +-- per-binding counterpart of the @dump-core@ plugin option: whereas +-- @dump-core@ dumps the Core of the /whole module/ after each pass, +-- 'DumpCorePasses' dumps only the annotated binding (and the closure of top +-- level bindings it reaches). +-- +-- The files are written under the same directory as the 'DumpCore' annotation +-- output, using the same @\.\.@ prefix, with a per-pass +-- suffix (a two digit pass counter and the pass name) appended in the same +-- format that the @dump-core@ option uses for its per-pass files. For example, +-- a binding @myFunction@ in module @Data.Stream@ produces one file per pass: +-- +-- @ +-- fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.00-Initial.dump-simpl +-- fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.01-After-...\.dump-simpl +-- ... +-- @ +-- +-- This is useful for understanding how the Core of a single binding evolves +-- across the optimizer pipeline, without wading through the Core of the entire +-- module at every pass. +-- +-- @ +-- {-\# ANN myFunction DumpCorePasses #-} +-- @ +data DumpCorePasses = DumpCorePasses + deriving (Eq, Data) From cfd6e81b6f44e72b51fc845aeece0b1680367cb9 Mon Sep 17 00:00:00 2001 From: Harendra Kumar Date: Tue, 14 Jul 2026 23:31:45 +0530 Subject: [PATCH 5/5] Update docs for core dump location consolidation --- src/Fusion/Plugin/Types.hs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Fusion/Plugin/Types.hs b/src/Fusion/Plugin/Types.hs index 011e1aa..03190eb 100644 --- a/src/Fusion/Plugin/Types.hs +++ b/src/Fusion/Plugin/Types.hs @@ -206,15 +206,16 @@ newtype MaxCoreSize = MaxCoreSize Int -- pragma on the binding) that makes the plugin dump the optimized Core of that -- binding, after all fusion-plugin passes have run, to a file. -- --- The Core is written to a file under the @fusion-plugin-output@ directory, in --- a subdirectory named after the package being compiled. For example, a --- binding @myFunction@ in module @Data.Stream@ of package @my-pkg@ is written --- to --- @fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.dump-simpl@. +-- The Core is written to GHC's dump directory (as set by @-dumpdir@) if one is +-- set, otherwise to a per-package subdirectory of @fusion-plugin-output@. For +-- example, a binding @myFunction@ in module @Data.Stream@ of package @my-pkg@ +-- is written to @\\/Data.Stream.myFunction.dump-simpl@, or to +-- @fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.dump-simpl@ when no +-- dump directory is set. -- --- Note that the output directory is created in the current directory from --- where GHC is invoked, when building with cabal it is usually the directory --- in which the cabal file of the package resides. +-- Note that the @fusion-plugin-output@ fallback directory is created in the +-- current directory from where GHC is invoked; when building with cabal that is +-- usually the directory in which the cabal file of the package resides. -- -- This is useful for inspecting the final Core of a hot binding without having -- to wade through the Core of the entire module. @@ -233,15 +234,23 @@ data DumpCore = DumpCore -- 'DumpCorePasses' dumps only the annotated binding (and the closure of top -- level bindings it reaches). -- --- The files are written under the same directory as the 'DumpCore' annotation --- output, using the same @\.\.@ prefix, with a per-pass --- suffix (a two digit pass counter and the pass name) appended in the same --- format that the @dump-core@ option uses for its per-pass files. For example, --- a binding @myFunction@ in module @Data.Stream@ produces one file per pass: +-- The files are written to the same directory and with the same +-- @\.@ prefix as the 'DumpCore' annotation output -- GHC's dump +-- directory (as set by @-dumpdir@) if one is set, otherwise a per-package +-- subdirectory of @fusion-plugin-output@ -- so all of a module's fusion-plugin +-- dumps cluster together under one prefix. Each file is named +-- @\.\.\.dump-simpl@, where the per-pass suffix (a +-- two digit pass counter and the pass name) is the same one the @dump-core@ +-- option uses for its per-pass files. For example, a binding @myFunction@ in +-- module @Data.Stream@ of package @my-pkg@ produces one file per pass: -- -- @ +-- -- with @-dumpdir dir@: +-- dir\/Data.Stream.myFunction.00-Initial.dump-simpl +-- dir\/Data.Stream.myFunction.01-After-...\.dump-simpl +-- ... +-- -- otherwise: -- fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.00-Initial.dump-simpl --- fusion-plugin-output\/my-pkg\/Data.Stream.myFunction.01-After-...\.dump-simpl -- ... -- @ --