    module Dwarf (
      dwarfGen
      ) where

    import CLabel
    import CmmExpr         ( GlobalReg(..) )
    import Config          ( cProjectName, cProjectVersion )
    import CoreSyn         ( Tickish(..) )
    import Debug
    import DynFlags
    import FastString
    import Module
    import Outputable
    import Platform
    import Unique
    import UniqSupply

    import Dwarf.Constants
    import Dwarf.Types

    import Data.Maybe
    import Data.List        ( sortBy )
    import Data.Ord         ( comparing )
    import qualified Data.Map as Map
    import System.FilePath
    import System.Directory ( getCurrentDirectory )

    import qualified Compiler.Hoopl as H

    -- | Generate DWARF/debug information
    dwarfGen :: DynFlags -> ModLocation -> UniqSupply -> [DebugBlock]
                -> IO (SDoc, UniqSupply)
    dwarfGen df modLoc us blocks = do

      -- Convert debug data structures to DWARF info records
      -- We strip out block information, as it is not currently useful for
      -- anything. In future we might want to only do this for -g1.
      let procs = debugSplitProcs blocks
          stripBlocks dbg = dbg { dblBlocks = [] }
      compPath <- getCurrentDirectory
      let dwarfUnit = DwarfCompileUnit
            { dwChildren = map (procToDwarf df) (map stripBlocks procs)
            , dwName = fromMaybe "" (ml_hs_file modLoc)
            , dwCompDir = addTrailingPathSeparator compPath
            , dwProducer = cProjectName ++ " " ++ cProjectVersion
            , dwLineLabel = dwarfLineLabel
            }

      -- Check whether we have any source code information, so we do not
      -- end up writing a pointer to an empty .debug_line section
      -- (dsymutil on Mac Os gets confused by this).
      let haveSrcIn blk = isJust (dblSourceTick blk) && isJust (dblPosition blk)
                          || any haveSrcIn (dblBlocks blk)
          haveSrc = any haveSrcIn procs

      -- .debug_abbrev section: Declare the format we're using
      let abbrevSct = pprAbbrevDecls haveSrc

      -- .debug_info section: Information records on procedures and blocks
      let (unitU, us') = takeUniqFromSupply us
          infoSct = vcat [ dwarfInfoSection
                         , compileUnitHeader unitU
                         , pprDwarfInfo haveSrc dwarfUnit
                         , compileUnitFooter unitU
                         ]

      -- .debug_line section: Generated mainly by the assembler, but we
      -- need to label it
      let lineSct = dwarfLineSection $$
                    ptext dwarfLineLabel <> colon

      -- .debug_frame section: Information about the layout of the GHC stack
      let (framesU, us'') = takeUniqFromSupply us'
          frameSct = dwarfFrameSection $$
                     ptext dwarfFrameLabel <> colon $$
                     pprDwarfFrame (debugFrame framesU procs)

      return (infoSct $$ abbrevSct $$ lineSct $$ frameSct, us'')

    -- | Header for a compilation unit, establishing global format
    -- parameters
    compileUnitHeader :: Unique -> SDoc
    compileUnitHeader unitU = sdocWithPlatform $ \plat ->
      let cuLabel = mkAsmTempLabel unitU
          length = ppr (mkAsmTempEndLabel cuLabel) <> char '-' <> ppr cuLabel
      in vcat [ ptext (sLit "\t.long ") <> length  -- compilation unit size
              , ppr cuLabel <> colon
              , ptext (sLit "\t.word 3")           -- DWARF version
              , sectionOffset dwarfAbbrevLabel dwarfAbbrevLabel
                                                   -- abbrevs offset
              , ptext (sLit "\t.byte ") <> ppr (platformWordSize plat) -- word size
              ]

    -- | Compilation unit footer, mainly establishing size of debug sections
    compileUnitFooter :: Unique -> SDoc
    compileUnitFooter unitU =
      let cuEndLabel = mkAsmTempEndLabel $ mkAsmTempLabel unitU
      in ppr cuEndLabel <> colon

    -- | Splits the blocks by procedures. In the result all nested blocks
    -- will come from the same procedure as the top-level block.
    debugSplitProcs :: [DebugBlock] -> [DebugBlock]
    debugSplitProcs b = concat $ H.mapElems $ mergeMaps $ map split b
      where mergeMaps = foldr (H.mapUnionWithKey (const (++))) H.mapEmpty
            split :: DebugBlock -> H.LabelMap [DebugBlock]
            split blk = H.mapInsert prc [blk {dblBlocks = own_blks}] nested
              where prc = dblProcedure blk
                    own_blks = fromMaybe [] $ H.mapLookup prc nested
                    nested = mergeMaps $ map split $ dblBlocks blk
            -- Note that we are rebuilding the tree here, so tick scopes
            -- might change. We could fix that - but we actually only care
            -- about dblSourceTick in the result, so this is okay.

    -- | Generate DWARF info for a procedure debug block
    procToDwarf :: DynFlags -> DebugBlock -> DwarfInfo
    procToDwarf df prc
      = DwarfSubprogram { dwChildren = foldr blockToDwarf [] $ dblBlocks prc
                        , dwName     = case dblSourceTick prc of
                             Just s@SourceNote{} -> sourceName s
                             _otherwise -> showSDocDump df $ ppr $ dblLabel prc
                        , dwLabel    = dblCLabel prc
                        }

    -- | Generate DWARF info for a block
    blockToDwarf :: DebugBlock -> [DwarfInfo] -> [DwarfInfo]
    blockToDwarf blk dws
      | isJust (dblPosition blk) = dw : dws
      | otherwise                = nested ++ dws -- block was optimized out, flatten
      where nested = foldr blockToDwarf [] $ dblBlocks blk
            dw = DwarfBlock { dwChildren = nested
                            , dwLabel    = dblCLabel blk
                            , dwMarker   = mkAsmTempLabel (dblLabel blk)
                            }

    -- | Generates the data for the debug frame section, which encodes the
    -- desired stack unwind behaviour for the debugger
    debugFrame :: Unique -> [DebugBlock] -> DwarfFrame
    debugFrame u procs
      = DwarfFrame { dwCieLabel = mkAsmTempLabel u
                   , dwCieInit  = initUws
                   , dwCieProcs = map (procToFrame initUws) procs
                   }
      where initUws = Map.fromList [(Sp, UwReg Sp 0)]

    -- | Generates unwind information for a procedure debug block
    procToFrame :: UnwindTable -> DebugBlock -> DwarfFrameProc
    procToFrame initUws blk
      = DwarfFrameProc { dwFdeProc    = dblCLabel blk
                       , dwFdeHasInfo = dblHasInfoTbl blk
                       , dwFdeBlocks  = map (uncurry blockToFrame) blockUws
                       }
      where blockUws :: [(DebugBlock, UnwindTable)]
            blockUws = map snd $ sortBy (comparing fst) $ flatten initUws blk
            flatten uws0 b@DebugBlock{ dblPosition=pos, dblUnwind=uws,
                                       dblBlocks=blocks }
              | Just p <- pos  = (p, (b, uws')):nested
              | otherwise      = nested -- block was optimized out
              where uws'   = uws `Map.union` uws0
                    nested = concatMap (flatten uws') blocks

    blockToFrame :: DebugBlock -> UnwindTable -> DwarfFrameBlock
    blockToFrame blk uws
      = DwarfFrameBlock { dwFdeBlock      = mkAsmTempLabel $ dblLabel blk
                        , dwFdeBlkHasInfo = dblHasInfoTbl blk
                        , dwFdeUnwind     = uws
                        }