diff --git a/app/Main.hs b/app/Main.hs
index 3624342be64cf28e0d4605ce1c3884590321e2e8..3127c0ee36055decbb8c457d767c834e832e0b9d 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,6 +1,6 @@
 module Main where
 
-import qualified HpcMain as HPC
+import qualified Trace.Hpc.Main as HPC
 
 main :: IO ()
-main = HPC.main
\ No newline at end of file
+main = HPC.main
diff --git a/hpc-bin.cabal b/hpc-bin.cabal
index 58bad0470a7fa46d2630e1a3c6d6a0a16f6ebf79..a52430ec769aaf13c26a18fe050abcf280c6d1cd 100644
--- a/hpc-bin.cabal
+++ b/hpc-bin.cabal
@@ -29,17 +29,20 @@ executable hpc
       app,
       src
     main-is: Main.hs
-    other-modules: HpcParser
-                   HpcCombine
-                   HpcDraft
-                   HpcFlags
-                   HpcLexer
-                   HpcMarkup
-                   HpcOverlay
-                   HpcReport
-                   HpcShowTix
-                   HpcUtils
-                   HpcMain
+    other-modules: Trace.Hpc.Parser
+                   Trace.Hpc.Combine
+                   Trace.Hpc.Draft
+                   Trace.Hpc.Flags
+                   Trace.Hpc.Lexer
+                   Trace.Hpc.Plugin
+                   Trace.Hpc.Markup
+                   Trace.Hpc.Map
+                   Trace.Hpc.Sum
+                   Trace.Hpc.Overlay
+                   Trace.Hpc.Report
+                   Trace.Hpc.ShowTix
+                   Trace.Hpc.Utils
+                   Trace.Hpc.Main
                    Paths_hpc_bin
     autogen-modules: Paths_hpc_bin
 
diff --git a/src/HpcCombine.hs b/src/HpcCombine.hs
deleted file mode 100644
index 7dc568ddcb84308e70b167fd60956d14f81827e2..0000000000000000000000000000000000000000
--- a/src/HpcCombine.hs
+++ /dev/null
@@ -1,150 +0,0 @@
----------------------------------------------------------
--- The main program for the hpc-add tool, part of HPC.
--- Andy Gill, Oct 2006
----------------------------------------------------------
-
-module HpcCombine (sumPlugin, combinePlugin, mapPlugin) where
-
-import Control.DeepSeq
-import Control.Monad
-import qualified Data.Map as Map
-import qualified Data.Set as Set
-import HpcFlags
-import Trace.Hpc.Tix
-
-------------------------------------------------------------------------------
-
-sumOptions :: FlagOptSeq
-sumOptions =
-  excludeOpt
-    . includeOpt
-    . outputOpt
-    . unionModuleOpt
-    . verbosityOpt
-
-sumPlugin :: Plugin
-sumPlugin =
-  Plugin
-    { name = "sum",
-      usage = "[OPTION] .. <TIX_FILE> [<TIX_FILE> [<TIX_FILE> ..]]",
-      options = sumOptions,
-      summary = "Sum multiple .tix files in a single .tix file",
-      implementation = sumMain
-    }
-
-combineOptions :: FlagOptSeq
-combineOptions =
-  excludeOpt
-    . includeOpt
-    . outputOpt
-    . combineFunOpt
-    . combineFunOptInfo
-    . unionModuleOpt
-    . verbosityOpt
-
-combinePlugin :: Plugin
-combinePlugin =
-  Plugin
-    { name = "combine",
-      usage = "[OPTION] .. <TIX_FILE> <TIX_FILE>",
-      options = combineOptions,
-      summary = "Combine two .tix files in a single .tix file",
-      implementation = combineMain
-    }
-
-mapOptions :: FlagOptSeq
-mapOptions =
-  excludeOpt
-    . includeOpt
-    . outputOpt
-    . mapFunOpt
-    . mapFunOptInfo
-    . unionModuleOpt
-    . verbosityOpt
-
-mapPlugin :: Plugin
-mapPlugin =
-  Plugin
-    { name = "map",
-      usage = "[OPTION] .. <TIX_FILE> ",
-      options = mapOptions,
-      summary = "Map a function over a single .tix file",
-      implementation = mapMain
-    }
-
-------------------------------------------------------------------------------
-
-sumMain :: Flags -> [String] -> IO ()
-sumMain _ [] = hpcError sumPlugin "no .tix file specified"
-sumMain flags (first_file : more_files) = do
-  Just tix <- readTix first_file
-
-  tix' <- foldM (mergeTixFile flags (+)) (filterTix flags tix) more_files
-
-  case outputFile flags of
-    "-" -> print tix'
-    out -> writeTix out tix'
-
-combineMain :: Flags -> [String] -> IO ()
-combineMain flags [first_file, second_file] = do
-  let f = theCombineFun (combineFun flags)
-
-  Just tix1 <- readTix first_file
-  Just tix2 <- readTix second_file
-
-  let tix = mergeTix (mergeModule flags) f (filterTix flags tix1) (filterTix flags tix2)
-
-  case outputFile flags of
-    "-" -> print tix
-    out -> writeTix out tix
-combineMain _ _ = hpcError combinePlugin "need exactly two .tix files to combine"
-
-mapMain :: Flags -> [String] -> IO ()
-mapMain flags [first_file] = do
-  let f = thePostFun (postFun flags)
-
-  Just tix <- readTix first_file
-
-  let (Tix inside_tix) = filterTix flags tix
-  let tix' = Tix [TixModule m p i (map f t) | TixModule m p i t <- inside_tix]
-
-  case outputFile flags of
-    "-" -> print tix'
-    out -> writeTix out tix'
-mapMain _ [] = hpcError mapPlugin "no .tix file specified"
-mapMain _ _ = hpcError mapPlugin "to many .tix files specified"
-
-mergeTixFile :: Flags -> (Integer -> Integer -> Integer) -> Tix -> String -> IO Tix
-mergeTixFile flags fn tix file_name = do
-  Just new_tix <- readTix file_name
-  return $! force $ mergeTix (mergeModule flags) fn tix (filterTix flags new_tix)
-
--- could allow different numbering on the module info,
--- as long as the total is the same; will require normalization.
-
-mergeTix :: MergeFun -> (Integer -> Integer -> Integer) -> Tix -> Tix -> Tix
-mergeTix modComb f (Tix t1) (Tix t2) =
-  Tix
-    [ case (Map.lookup m fm1, Map.lookup m fm2) of
-        -- todo, revisit the semantics of this combination
-        (Just (TixModule _ hash1 len1 tix1), Just (TixModule _ hash2 len2 tix2))
-          | hash1 /= hash2
-              || length tix1 /= length tix2
-              || len1 /= length tix1
-              || len2 /= length tix2 ->
-              error $ "mismatched in module " ++ m
-          | otherwise ->
-              TixModule m hash1 len1 (zipWith f tix1 tix2)
-        (Just m1, Nothing) ->
-          m1
-        (Nothing, Just m2) ->
-          m2
-        _ -> error "impossible"
-      | m <- Set.toList (theMergeFun modComb m1s m2s)
-    ]
-  where
-    m1s = Set.fromList $ map tixModuleName t1
-    m2s = Set.fromList $ map tixModuleName t2
-
-    fm1 = Map.fromList [(tixModuleName tix, tix) | tix <- t1]
-    fm2 = Map.fromList [(tixModuleName tix, tix) | tix <- t2]
diff --git a/src/Trace/Hpc/Combine.hs b/src/Trace/Hpc/Combine.hs
new file mode 100644
index 0000000000000000000000000000000000000000..95da809fb6a0ae7d4989e918a94f97b912ddbfe5
--- /dev/null
+++ b/src/Trace/Hpc/Combine.hs
@@ -0,0 +1,49 @@
+-- |
+-- Module             : Trace.Hpc.Combine
+-- Description        : The subcommand @hpc combine@
+-- Copyright          : Andy Gill, 2006
+-- License            : BSD-3-Clause
+module Trace.Hpc.Combine (combinePlugin) where
+
+import Control.DeepSeq
+import Control.Monad
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+import Trace.Hpc.Flags
+import Trace.Hpc.Plugin
+import Trace.Hpc.Tix
+import Trace.Hpc.Utils
+
+combineOptions :: FlagOptSeq
+combineOptions =
+  excludeOpt
+    . includeOpt
+    . outputOpt
+    . combineFunOpt
+    . combineFunOptInfo
+    . unionModuleOpt
+    . verbosityOpt
+
+combinePlugin :: Plugin
+combinePlugin =
+  Plugin
+    { name = "combine",
+      usage = "[OPTION] .. <TIX_FILE> <TIX_FILE>",
+      options = combineOptions,
+      summary = "Combine two .tix files in a single .tix file",
+      implementation = combineMain
+    }
+
+combineMain :: Flags -> [String] -> IO ()
+combineMain flags [first_file, second_file] = do
+  let f = theCombineFun (combineFun flags)
+
+  Just tix1 <- readTix first_file
+  Just tix2 <- readTix second_file
+
+  let tix = mergeTix (mergeModule flags) f (filterTix flags tix1) (filterTix flags tix2)
+
+  case outputFile flags of
+    "-" -> print tix
+    out -> writeTix out tix
+combineMain _ _ = hpcError combinePlugin "need exactly two .tix files to combine"
diff --git a/src/HpcDraft.hs b/src/Trace/Hpc/Draft.hs
similarity index 94%
rename from src/HpcDraft.hs
rename to src/Trace/Hpc/Draft.hs
index ea4acc1f980f83d60ae8af5af813de960098c5d9..9d47b8acae66e4544afdeca3a125d9b5cfd069ac 100644
--- a/src/HpcDraft.hs
+++ b/src/Trace/Hpc/Draft.hs
@@ -1,13 +1,18 @@
-module HpcDraft (draftPlugin) where
+-- |
+-- Module             : Trace.Hpc.Draft
+-- Description        : The subcommand @hpc draft@
+-- License            : BSD-3-Clause
+module Trace.Hpc.Draft (draftPlugin) where
 
 import qualified Data.Map as Map
 import qualified Data.Set as Set
 import Data.Tree
-import HpcFlags
-import HpcUtils
+import Trace.Hpc.Flags
 import Trace.Hpc.Mix
+import Trace.Hpc.Plugin
 import Trace.Hpc.Tix
 import Trace.Hpc.Util
+import Trace.Hpc.Utils
 
 ------------------------------------------------------------------------------
 
diff --git a/src/HpcFlags.hs b/src/Trace/Hpc/Flags.hs
similarity index 90%
rename from src/HpcFlags.hs
rename to src/Trace/Hpc/Flags.hs
index c7952677423cb814ec17207e878b1b3c1c933c88..361f91f2579a07cd30190891bb3a49022bea9a99 100644
--- a/src/HpcFlags.hs
+++ b/src/Trace/Hpc/Flags.hs
@@ -1,6 +1,9 @@
--- (c) 2007 Andy Gill
-
-module HpcFlags where
+-- |
+-- Module             : Trace.Hpc.Flags
+-- Description        : Commandline flags for hpc
+-- Copyright          : Andy Gill, 2007
+-- License            : BSD-3-Clause
+module Trace.Hpc.Flags where
 
 import Data.Char
 import qualified Data.Set as Set
@@ -233,38 +236,6 @@ readMixWithFlags flags =
         hpcDir <- hpcDirs flags
     ]
 
--------------------------------------------------------------------------------
-
-commandUsage :: Plugin -> IO ()
-commandUsage plugin =
-  putStrLn $
-    "Usage: hpc "
-      ++ name plugin
-      ++ " "
-      ++ usage plugin
-      ++ "\n"
-      ++ summary plugin
-      ++ "\n"
-      ++ if null (options plugin [])
-        then ""
-        else usageInfo "\n\nOptions:\n" (options plugin [])
-
-hpcError :: Plugin -> String -> IO a
-hpcError plugin msg = do
-  putStrLn $ "Error: " ++ msg
-  commandUsage plugin
-  exitFailure
-
--------------------------------------------------------------------------------
-
-data Plugin = Plugin
-  { name :: String,
-    usage :: String,
-    options :: FlagOptSeq,
-    summary :: String,
-    implementation :: Flags -> [String] -> IO ()
-  }
-
 ------------------------------------------------------------------------------
 
 -- filterModules takes a list of candidate modules,
diff --git a/src/HpcLexer.hs b/src/Trace/Hpc/Lexer.hs
similarity index 91%
rename from src/HpcLexer.hs
rename to src/Trace/Hpc/Lexer.hs
index 2cee0fde934650e4b34ff954f8300c1a8b723d6f..fe41c46cc899bc362b1820e059ff3368db4291c1 100644
--- a/src/HpcLexer.hs
+++ b/src/Trace/Hpc/Lexer.hs
@@ -1,4 +1,8 @@
-module HpcLexer where
+-- |
+-- Module             : Trace.Hpc.Lexer
+-- Description        : A lexer for overlay files use by @hpc overlay@
+-- License            : BSD-3-Clause
+module Trace.Hpc.Lexer where
 
 import Data.Char
 
diff --git a/src/HpcMain.hs b/src/Trace/Hpc/Main.hs
similarity index 92%
rename from src/HpcMain.hs
rename to src/Trace/Hpc/Main.hs
index 74c98ceadc31027eadf25d61b362d4fa00be1c90..11b005b5da55651e0039f693342dc15024634ee6 100644
--- a/src/HpcMain.hs
+++ b/src/Trace/Hpc/Main.hs
@@ -1,29 +1,34 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TupleSections #-}
 
-module HpcMain (main) where
-
--- (c) 2007 Andy Gill
--- Main driver for Hpc
+-- |
+-- Module             : Trace.Hpc.Main
+-- Description        : The main driver for Hpc
+-- Copyright          : Andy Gill, 2007
+-- License            : BSD-3-Clause
+module Trace.Hpc.Main (main) where
 
 import Control.Monad
 import Data.Bifunctor
-import Data.List ( intercalate, partition, uncons )
+import Data.List (intercalate, partition, uncons)
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.Maybe
 import Data.Version
-import HpcCombine
-import HpcDraft
-import HpcFlags
-import HpcMarkup
-import HpcOverlay
-import HpcReport
-import HpcShowTix
 import Paths_hpc_bin
 import System.Console.GetOpt
 import System.Directory
 import System.Environment
 import System.Exit
+import Trace.Hpc.Combine
+import Trace.Hpc.Draft
+import Trace.Hpc.Flags
+import Trace.Hpc.Map
+import Trace.Hpc.Markup
+import Trace.Hpc.Overlay
+import Trace.Hpc.Plugin
+import Trace.Hpc.Report
+import Trace.Hpc.ShowTix
+import Trace.Hpc.Sum
 
 ------------------------------------------------------------------------------
 
diff --git a/src/Trace/Hpc/Map.hs b/src/Trace/Hpc/Map.hs
new file mode 100644
index 0000000000000000000000000000000000000000..66f30b50362488d670dd4d3b44c46fbf5340b2a7
--- /dev/null
+++ b/src/Trace/Hpc/Map.hs
@@ -0,0 +1,45 @@
+-- |
+-- Module             : Trace.Hpc.Map
+-- Description        : The subcommand @hpc map@
+-- Copyright          : Andy Gill, 2006
+-- License            : BSD-3-Clause
+module Trace.Hpc.Map (mapPlugin) where
+
+import Trace.Hpc.Flags
+import Trace.Hpc.Plugin
+import Trace.Hpc.Tix
+
+mapOptions :: FlagOptSeq
+mapOptions =
+  excludeOpt
+    . includeOpt
+    . outputOpt
+    . mapFunOpt
+    . mapFunOptInfo
+    . unionModuleOpt
+    . verbosityOpt
+
+mapPlugin :: Plugin
+mapPlugin =
+  Plugin
+    { name = "map",
+      usage = "[OPTION] .. <TIX_FILE> ",
+      options = mapOptions,
+      summary = "Map a function over a single .tix file",
+      implementation = mapMain
+    }
+
+mapMain :: Flags -> [String] -> IO ()
+mapMain flags [first_file] = do
+  let f = thePostFun (postFun flags)
+
+  Just tix <- readTix first_file
+
+  let (Tix inside_tix) = filterTix flags tix
+  let tix' = Tix [TixModule m p i (map f t) | TixModule m p i t <- inside_tix]
+
+  case outputFile flags of
+    "-" -> print tix'
+    out -> writeTix out tix'
+mapMain _ [] = hpcError mapPlugin "no .tix file specified"
+mapMain _ _ = hpcError mapPlugin "to many .tix files specified"
diff --git a/src/HpcMarkup.hs b/src/Trace/Hpc/Markup.hs
similarity index 98%
rename from src/HpcMarkup.hs
rename to src/Trace/Hpc/Markup.hs
index 1c787db9be8c8adace23c8dd93ad4068879953c3..884d02065dd3c609ba305a79ae37e79fdd8ebe48 100644
--- a/src/HpcMarkup.hs
+++ b/src/Trace/Hpc/Markup.hs
@@ -1,22 +1,23 @@
----------------------------------------------------------
--- The main program for the hpc-markup tool, part of HPC.
--- Andy Gill and Colin Runciman, June 2006
----------------------------------------------------------
-
-module HpcMarkup (markupPlugin) where
+-- |
+-- Module             : Trace.Hpc.Markup
+-- Description        : The subcommand @hpc markup@
+-- Copyright          : Andy Gill and Colin Runciman, 2006
+-- License            : BSD-3-Clause
+module Trace.Hpc.Markup (markupPlugin) where
 
 import Control.Monad
 import Data.Array
-import Data.List ( find, sortBy )
+import Data.List (find, sortBy)
 import Data.Maybe
 import Data.Semigroup as Semi
 import qualified Data.Set as Set
-import HpcFlags
-import HpcUtils
 import System.FilePath
+import Trace.Hpc.Flags
 import Trace.Hpc.Mix
+import Trace.Hpc.Plugin
 import Trace.Hpc.Tix
 import Trace.Hpc.Util
+import Trace.Hpc.Utils
 
 ------------------------------------------------------------------------------
 
diff --git a/src/HpcOverlay.hs b/src/Trace/Hpc/Overlay.hs
similarity index 94%
rename from src/HpcOverlay.hs
rename to src/Trace/Hpc/Overlay.hs
index 831edb8b246e0fcda3512b9a0894de7e4c2f6b77..77b882310e0d29ff2c090a3d842b1c9c25822001 100644
--- a/src/HpcOverlay.hs
+++ b/src/Trace/Hpc/Overlay.hs
@@ -1,13 +1,18 @@
-module HpcOverlay where
+-- |
+-- Module             : Trace.Hpc.Overlay
+-- Description        : The subcommand @hpc overlay@
+-- License            : BSD-3-Clause
+module Trace.Hpc.Overlay where
 
 import qualified Data.Map as Map
 import Data.Tree
-import HpcFlags
-import HpcParser
-import HpcUtils
+import Trace.Hpc.Flags
 import Trace.Hpc.Mix
+import Trace.Hpc.Parser
+import Trace.Hpc.Plugin
 import Trace.Hpc.Tix
 import Trace.Hpc.Util
+import Trace.Hpc.Utils
 
 ------------------------------------------------------------------------------
 
diff --git a/src/HpcParser.y b/src/Trace/Hpc/Parser.y
similarity index 97%
rename from src/HpcParser.y
rename to src/Trace/Hpc/Parser.y
index ea660893ad2afbfe8ef9eb89e83881ba86b05cc4..92a24867c66fee9dafbaa194d9683832ca5657a9 100644
--- a/src/HpcParser.y
+++ b/src/Trace/Hpc/Parser.y
@@ -1,7 +1,7 @@
 {
-module HpcParser where
+module Trace.Hpc.Parser where
 
-import HpcLexer
+import Trace.Hpc.Lexer
 }
 
 %name parser
diff --git a/src/Trace/Hpc/Plugin.hs b/src/Trace/Hpc/Plugin.hs
new file mode 100644
index 0000000000000000000000000000000000000000..c3f22bdaa26b1135a143e80779f08c237b118576
--- /dev/null
+++ b/src/Trace/Hpc/Plugin.hs
@@ -0,0 +1,37 @@
+-- |
+-- Module             : Trace.Hpc.Plugin
+-- Description        : The plugin type used by subcommands
+-- License            : BSD-3-Clause
+module Trace.Hpc.Plugin where
+
+import System.Console.GetOpt
+import System.Exit
+import Trace.Hpc.Flags
+
+data Plugin = Plugin
+  { name :: String,
+    usage :: String,
+    options :: FlagOptSeq,
+    summary :: String,
+    implementation :: Flags -> [String] -> IO ()
+  }
+
+commandUsage :: Plugin -> IO ()
+commandUsage plugin =
+  putStrLn $
+    "Usage: hpc "
+      ++ name plugin
+      ++ " "
+      ++ usage plugin
+      ++ "\n"
+      ++ summary plugin
+      ++ "\n"
+      ++ if null (options plugin [])
+        then ""
+        else usageInfo "\n\nOptions:\n" (options plugin [])
+
+hpcError :: Plugin -> String -> IO a
+hpcError plugin msg = do
+  putStrLn $ "Error: " ++ msg
+  commandUsage plugin
+  exitFailure
diff --git a/src/HpcReport.hs b/src/Trace/Hpc/Report.hs
similarity index 97%
rename from src/HpcReport.hs
rename to src/Trace/Hpc/Report.hs
index ca01da43b8870fb0050e301b764b0f7157a4b8e8..d7fa1be114d35b06ecf3c4b4939c779ae570b4fd 100644
--- a/src/HpcReport.hs
+++ b/src/Trace/Hpc/Report.hs
@@ -1,16 +1,17 @@
----------------------------------------------------------
--- The main program for the hpc-report tool, part of HPC.
--- Colin Runciman and Andy Gill, June 2006
----------------------------------------------------------
-
-module HpcReport (reportPlugin) where
+-- |
+-- Module             : Trace.Hpc.Report
+-- Description        : The subcommand @hpc report@
+-- Copyright          : Andy Gill and Colin Runciman, 2006
+-- License            : BSD-3-Clause
+module Trace.Hpc.Report (reportPlugin) where
 
 import Control.Monad hiding (guard)
 import Data.Function
 import Data.List (intercalate, sort, sortBy)
 import qualified Data.Set as Set
-import HpcFlags
+import Trace.Hpc.Flags
 import Trace.Hpc.Mix
+import Trace.Hpc.Plugin
 import Trace.Hpc.Tix
 import Prelude hiding (exp)
 
diff --git a/src/HpcShowTix.hs b/src/Trace/Hpc/ShowTix.hs
similarity index 89%
rename from src/HpcShowTix.hs
rename to src/Trace/Hpc/ShowTix.hs
index bb346219f65f589de20a356dc18d384c175c1637..4eeb9564748f7a80b4878a088020d0dc0ec88367 100644
--- a/src/HpcShowTix.hs
+++ b/src/Trace/Hpc/ShowTix.hs
@@ -1,8 +1,13 @@
-module HpcShowTix (showtixPlugin) where
+-- |
+-- Module             : Trace.Hpc.ShowTix
+-- Description        : The subcommand @hpc show@
+-- License            : BSD-3-Clause
+module Trace.Hpc.ShowTix (showtixPlugin) where
 
 import qualified Data.Set as Set
-import HpcFlags
+import Trace.Hpc.Flags
 import Trace.Hpc.Mix
+import Trace.Hpc.Plugin
 import Trace.Hpc.Tix
 
 ------------------------------------------------------------------------------
diff --git a/src/Trace/Hpc/Sum.hs b/src/Trace/Hpc/Sum.hs
new file mode 100644
index 0000000000000000000000000000000000000000..3416e644c092b0207942b2a056d7e5b6a2697732
--- /dev/null
+++ b/src/Trace/Hpc/Sum.hs
@@ -0,0 +1,47 @@
+-- |
+-- Module             : Trace.Hpc.Sum
+-- Description        : The subcommand @hpc sum@
+-- Copyright          : Andy Gill, 2006
+-- License            : BSD-3-Clause
+module Trace.Hpc.Sum (sumPlugin) where
+
+import Control.DeepSeq
+import Control.Monad
+import Trace.Hpc.Flags
+import Trace.Hpc.Plugin
+import Trace.Hpc.Tix
+import Trace.Hpc.Utils
+
+sumOptions :: FlagOptSeq
+sumOptions =
+  excludeOpt
+    . includeOpt
+    . outputOpt
+    . unionModuleOpt
+    . verbosityOpt
+
+sumPlugin :: Plugin
+sumPlugin =
+  Plugin
+    { name = "sum",
+      usage = "[OPTION] .. <TIX_FILE> [<TIX_FILE> [<TIX_FILE> ..]]",
+      options = sumOptions,
+      summary = "Sum multiple .tix files in a single .tix file",
+      implementation = sumMain
+    }
+
+sumMain :: Flags -> [String] -> IO ()
+sumMain _ [] = hpcError sumPlugin "no .tix file specified"
+sumMain flags (first_file : more_files) = do
+  Just tix <- readTix first_file
+
+  tix' <- foldM (mergeTixFile flags (+)) (filterTix flags tix) more_files
+
+  case outputFile flags of
+    "-" -> print tix'
+    out -> writeTix out tix'
+
+mergeTixFile :: Flags -> (Integer -> Integer -> Integer) -> Tix -> String -> IO Tix
+mergeTixFile flags fn tix file_name = do
+  Just new_tix <- readTix file_name
+  return $! force $ mergeTix (mergeModule flags) fn tix (filterTix flags new_tix)
diff --git a/src/HpcUtils.hs b/src/Trace/Hpc/Utils.hs
similarity index 53%
rename from src/HpcUtils.hs
rename to src/Trace/Hpc/Utils.hs
index a3ee5a66bc273171db5399086d3a3db2011720ea..bf0b608452d9aa30882b085a6460072ee46b200c 100644
--- a/src/HpcUtils.hs
+++ b/src/Trace/Hpc/Utils.hs
@@ -1,7 +1,15 @@
-module HpcUtils where
+-- |
+-- Module             : Trace.Hpc.Utils
+-- Description        : Utility functions for hpc-bin
+-- License            : BSD-3-Clause
+module Trace.Hpc.Utils where
 
+import Control.DeepSeq
 import qualified Data.Map as Map
+import qualified Data.Set as Set
 import System.FilePath
+import Trace.Hpc.Flags
+import Trace.Hpc.Tix
 import Trace.Hpc.Util
 
 ------------------------------------------------------------------------------
@@ -46,3 +54,30 @@ readFileFromPath err filename path0 = readTheFile path0
       catchIO
         (readFileUtf8 (dir </> filename))
         (\_ -> readTheFile dirs)
+
+mergeTix :: MergeFun -> (Integer -> Integer -> Integer) -> Tix -> Tix -> Tix
+mergeTix modComb f (Tix t1) (Tix t2) =
+  Tix
+    [ case (Map.lookup m fm1, Map.lookup m fm2) of
+        -- todo, revisit the semantics of this combination
+        (Just (TixModule _ hash1 len1 tix1), Just (TixModule _ hash2 len2 tix2))
+          | hash1 /= hash2
+              || length tix1 /= length tix2
+              || len1 /= length tix1
+              || len2 /= length tix2 ->
+              error $ "mismatched in module " ++ m
+          | otherwise ->
+              TixModule m hash1 len1 (zipWith f tix1 tix2)
+        (Just m1, Nothing) ->
+          m1
+        (Nothing, Just m2) ->
+          m2
+        _ -> error "impossible"
+      | m <- Set.toList (theMergeFun modComb m1s m2s)
+    ]
+  where
+    m1s = Set.fromList $ map tixModuleName t1
+    m2s = Set.fromList $ map tixModuleName t2
+
+    fm1 = Map.fromList [(tixModuleName tix, tix) | tix <- t1]
+    fm2 = Map.fromList [(tixModuleName tix, tix) | tix <- t2]