packages feed

ghc-9.14.1: GHC/ByteCode/Breakpoints.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DerivingStrategies #-}

-- | Breakpoint information constructed during ByteCode generation.
--
-- Specifically, code-generation breakpoints are referred to as "internal
-- breakpoints", the internal breakpoint data for a module is stored in
-- 'InternalModBreaks', and is uniquely identified at runtime by an
-- 'InternalBreakpointId'.
--
-- See Note [ModBreaks vs InternalModBreaks] and Note [Breakpoint identifiers]
module GHC.ByteCode.Breakpoints
  ( -- * Internal Mod Breaks
    InternalModBreaks(..), CgBreakInfo(..)
  , mkInternalModBreaks, imodBreaks_module

    -- ** Internal breakpoint identifier
  , InternalBreakpointId(..), BreakInfoIndex
  , InternalBreakLoc(..)

    -- * Operations

    -- ** Internal-level operations
  , getInternalBreak

    -- ** Source-level information operations
  , getBreakLoc, getBreakVars, getBreakDecls, getBreakCCS
  , getBreakSourceId, getBreakSourceMod

    -- * Utils
  , seqInternalModBreaks

  )
  where

import GHC.Prelude
import GHC.Types.SrcLoc
import GHC.Types.Name.Occurrence
import Control.DeepSeq
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IM

import GHC.HsToCore.Breakpoints
import GHC.Iface.Syntax

import GHC.Unit.Module (Module)
import GHC.Utils.Outputable
import GHC.Utils.Panic
import Data.Array

{-
Note [ModBreaks vs InternalModBreaks]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'ModBreaks' and 'BreakpointId's must not to be confused with
'InternalModBreaks' and 'InternalBreakId's.

'ModBreaks' is constructed once during HsToCore from the information attached
to source-level breakpoint ticks and is never changed afterwards. A 'ModBreaks'
can be queried using 'BreakpointId's, which uniquely identifies a breakpoint
within the list of breakpoint information for a given module's 'ModBreaks'.

'InternalModBreaks' are constructed during bytecode generation and are indexed
by a 'InternalBreakpointId'. They contain all the information relevant to a
breakpoint for code generation that can be accessed during runtime execution
(such as a 'BreakArray' for triggering breakpoints). 'InternalBreakpointId's
are used at runtime to trigger and inspect breakpoints -- a 'BRK_FUN'
instruction receives 'InternalBreakpointId' as an argument.

We keep a mapping from 'InternalModBreaks' to a 'BreakpointId', which can then be used
to get source-level information about a breakpoint via the corresponding 'ModBreaks'.

Notably, 'InternalModBreaks' can contain entries for so-called internal
breakpoints, which do not necessarily have a source-level location attached to
it (i.e. do not have a matching entry in 'ModBreaks'). We may leverage this to
introduce breakpoints during code generation for features such as stepping-out.

Note [Breakpoint identifiers]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before optimization a breakpoint is identified uniquely with a tick module
and a tick index. See 'BreakpointId'. A tick module contains an array, indexed
with the tick indexes, which indicates breakpoint status.

When we generate ByteCode, we collect information for every breakpoint at
their *occurrence sites* (see CgBreakInfo) and these info
are stored in the ModIface of the occurrence module. Because of inlining, we
can't reuse the tick index to uniquely identify an occurrence; because of
cross-module inlining, we can't assume that the occurrence module is the same
as the tick module (#24712).

So every breakpoint occurrence gets assigned a module-unique *info index* and
we store it alongside the occurrence module (*info module*) in the
'InternalBreakpointId' datatype. This is the index that we use at runtime to
identify a breakpoint.

When the internal breakpoint has a matching tick-level breakpoint we can fetch
the related tick-level information by first looking up a mapping
@'InternalBreakpointId' -> 'BreakpointId'@ in @'CgBreakInfo'@.
-}

--------------------------------------------------------------------------------
-- * Internal breakpoint identifiers
--------------------------------------------------------------------------------

-- | Internal breakpoint info index
type BreakInfoIndex = Int

-- | Internal breakpoint identifier
--
-- Indexes into the structures in the @'InternalModBreaks'@ produced during ByteCode generation.
-- See Note [Breakpoint identifiers]
data InternalBreakpointId = InternalBreakpointId
  { ibi_info_mod   :: !Module         -- ^ Breakpoint info module
  , ibi_info_index :: !BreakInfoIndex -- ^ Breakpoint info index
  }
  deriving (Eq, Ord)

--------------------------------------------------------------------------------
-- * Internal Mod Breaks
--------------------------------------------------------------------------------

-- | Internal mod breaks store the runtime-relevant information of breakpoints.
--
-- Importantly, it maps 'InternalBreakpointId's to 'CgBreakInfo'.
--
-- 'InternalModBreaks' are constructed during bytecode generation and stored in
-- 'CompiledByteCode' afterwards.
data InternalModBreaks = InternalModBreaks
      { imodBreaks_breakInfo :: !(IntMap CgBreakInfo)
        -- ^ Access code-gen time information about a breakpoint, indexed by
        -- 'InternalBreakpointId'.

      , imodBreaks_modBreaks :: !ModBreaks
        -- ^ Store the ModBreaks for this module
        --
        -- Recall Note [Breakpoint identifiers]: for some module A, an
        -- *occurrence* of a breakpoint in A may have been inlined from some
        -- breakpoint *defined* in module B.
        --
        -- This 'ModBreaks' contains information regarding all the breakpoints
        -- defined in the module this 'InternalModBreaks' corresponds to. It
        -- /does not/ necessarily have information regarding all the breakpoint
        -- occurrences registered in 'imodBreaks_breakInfo'. Some of those
        -- occurrences may refer breakpoints inlined from other modules.
      }

-- | Construct an 'InternalModBreaks'.
--
-- INVARIANT: The given 'ModBreaks' correspond to the same module as this
-- 'InternalModBreaks' module (the first argument) and its breakpoint infos
-- (the @IntMap CgBreakInfo@ argument)
mkInternalModBreaks :: Module -> IntMap CgBreakInfo -> ModBreaks -> InternalModBreaks
mkInternalModBreaks mod im mbs =
  assertPpr (mod == modBreaks_module mbs)
    (text "Constructing InternalModBreaks with the ModBreaks of a different module!") $
      InternalModBreaks im mbs

-- | Get the module to which these 'InternalModBreaks' correspond
imodBreaks_module :: InternalModBreaks -> Module
imodBreaks_module = modBreaks_module . imodBreaks_modBreaks

-- | Information about a breakpoint that we know at code-generation time
-- In order to be used, this needs to be hydrated relative to the current HscEnv by
-- 'hydrateCgBreakInfo'. Everything here can be fully forced and that's critical for
-- preventing space leaks (see #22530)
data CgBreakInfo
   = CgBreakInfo
   { cgb_tyvars  :: ![IfaceTvBndr] -- ^ Type variables in scope at the breakpoint
   , cgb_vars    :: ![Maybe (IfaceIdBndr, Word)]
   , cgb_resty   :: !IfaceType
   , cgb_tick_id :: !(Either InternalBreakLoc BreakpointId)
     -- ^ This field records the original breakpoint tick identifier for this
     -- internal breakpoint info. It is used to convert a breakpoint
     -- *occurrence* index ('InternalBreakpointId') into a *definition* index
     -- ('BreakpointId').
     --
     -- The modules of breakpoint occurrence and breakpoint definition are not
     -- necessarily the same: See Note [Breakpoint identifiers].
     --
     -- If there is no original tick identifier (that is, the breakpoint was
     -- created during code generation), we re-use the BreakpointId of something else.
     -- It would also be reasonable to have an @Either something BreakpointId@
     -- for @cgb_tick_id@, but currently we can always re-use a source-level BreakpointId.
     -- In the case of step-out, see Note [Debugger: Stepout internal break locs]
   }
-- See Note [Syncing breakpoint info] in GHC.Runtime.Eval

-- | Breakpoints created during code generation don't have a source-level tick
-- location. Instead, we re-use an existing one.
newtype InternalBreakLoc = InternalBreakLoc { internalBreakLoc :: BreakpointId }
  deriving newtype (Eq, NFData, Outputable)

-- | Get an internal breakpoint info by 'InternalBreakpointId'
getInternalBreak :: InternalBreakpointId -> InternalModBreaks -> CgBreakInfo
getInternalBreak (InternalBreakpointId mod ix) imbs =
  assert_modules_match mod (imodBreaks_module imbs) $
    imodBreaks_breakInfo imbs IM.! ix

-- | Assert that the module in the 'InternalBreakpointId' and in
-- 'InternalModBreaks' match.
assert_modules_match :: Module -> Module -> a -> a
assert_modules_match ibi_mod imbs_mod =
  assertPpr (ibi_mod == imbs_mod)
    (text "Tried to query the InternalModBreaks of module" <+> ppr imbs_mod
        <+> text "with an InternalBreakpointId for module" <+> ppr ibi_mod)

--------------------------------------------------------------------------------
-- Tick-level Breakpoint information
--------------------------------------------------------------------------------

-- | Get the source module and tick index for this breakpoint
-- (as opposed to the module where this breakpoint occurs, which is in 'InternalBreakpointId')
getBreakSourceId :: InternalBreakpointId -> InternalModBreaks -> BreakpointId
getBreakSourceId (InternalBreakpointId ibi_mod ibi_ix) imbs =
  assert_modules_match ibi_mod (imodBreaks_module imbs) $
    let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
     in either internalBreakLoc id (cgb_tick_id cgb)

-- | Get the source module for this breakpoint (where the breakpoint is defined)
getBreakSourceMod :: InternalBreakpointId -> InternalModBreaks -> Module
getBreakSourceMod (InternalBreakpointId ibi_mod ibi_ix) imbs =
  assert_modules_match ibi_mod (imodBreaks_module imbs) $
    let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
     in either (bi_tick_mod . internalBreakLoc) bi_tick_mod (cgb_tick_id cgb)

-- | Get the source span for this breakpoint
getBreakLoc :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO SrcSpan
getBreakLoc = getBreakXXX modBreaks_locs

-- | Get the vars for this breakpoint
getBreakVars :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO [OccName]
getBreakVars = getBreakXXX modBreaks_vars

-- | Get the decls for this breakpoint
getBreakDecls :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO [String]
getBreakDecls = getBreakXXX modBreaks_decls

-- | Get the decls for this breakpoint
getBreakCCS :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO ((String, String))
getBreakCCS = getBreakXXX modBreaks_ccs

-- | Internal utility to access a ModBreaks field at a particular breakpoint index
--
-- Recall Note [Breakpoint identifiers]: the internal breakpoint module (the
-- *occurrence* module) doesn't necessarily match the module where the
-- tick breakpoint was defined with the relevant 'ModBreaks'.
--
-- When the tick module is the same as the internal module, we use the stored
-- 'ModBreaks'. When the tick module is different, we need to look up the
-- 'ModBreaks' in the HUG for that other module.
--
-- When there is no tick module (the breakpoint was generated at codegen), use
-- the function on internal mod breaks.
--
-- To avoid cyclic dependencies, we instead receive a function that looks up
-- the 'ModBreaks' given a 'Module'
getBreakXXX :: (ModBreaks -> Array BreakTickIndex a) -> (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO a
getBreakXXX view lookupModule (InternalBreakpointId ibi_mod ibi_ix) imbs =
  assert_modules_match ibi_mod (imodBreaks_module imbs) $ do
    let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
    case either internalBreakLoc id (cgb_tick_id cgb) of
      BreakpointId{bi_tick_mod, bi_tick_index}
        | bi_tick_mod == ibi_mod
        -> do
          let these_mbs = imodBreaks_modBreaks imbs
          return $ view these_mbs ! bi_tick_index
        | otherwise
        -> do
          other_mbs <- lookupModule bi_tick_mod
          return $ view other_mbs ! bi_tick_index

--------------------------------------------------------------------------------
-- Instances
--------------------------------------------------------------------------------

-- | Fully force an 'InternalModBreaks' value
seqInternalModBreaks :: InternalModBreaks -> ()
seqInternalModBreaks InternalModBreaks{..} =
    rnf (fmap seqCgBreakInfo imodBreaks_breakInfo)
  where
    seqCgBreakInfo :: CgBreakInfo -> ()
    seqCgBreakInfo CgBreakInfo{..} =
        rnf cgb_tyvars `seq`
        rnf cgb_vars `seq`
        rnf cgb_resty `seq`
        rnf cgb_tick_id

instance Outputable InternalBreakpointId where
  ppr InternalBreakpointId{..} =
    text "InternalBreakpointId" <+> ppr ibi_info_mod <+> ppr ibi_info_index

instance NFData InternalBreakpointId where
  rnf InternalBreakpointId{..} =
    rnf ibi_info_mod `seq` rnf ibi_info_index

instance Outputable CgBreakInfo where
   ppr info = text "CgBreakInfo" <+>
              parens (ppr (cgb_vars info) <+>
                      ppr (cgb_resty info) <+>
                      ppr (cgb_tick_id info))