packages feed

recover-rtti-0.5.1: src/Debug/RecoverRTTI/FlatClosure.hs

module Debug.RecoverRTTI.FlatClosure (
    FlatClosure(..)
  , getBoxedClosureData
    -- * Re-exports
  , Box(..)
  , asBox
  ) where

import Control.Exception (evaluate)
import Control.Monad
import GHC.Exts.Heap (Box(..), asBox)
import GHC.Exts.Heap qualified as H

-- | Flattened form of 'Closure' (with indirection nodes removed)
--
-- We only include the fields of 'Closure' that we are interested in.
--
-- TODO: For functions ('FunClosure', 'PAPClosure') we don't currently include
-- any information at all. We could potentially do better here.
data FlatClosure =
    -- | Constructor application
    ConstrClosure {
        ptrArgs :: [Box]
      , pkg     :: String
      , modl    :: String
      , name    :: String
      }

    -- | Functions
    --
    -- We map 'H.FunClosure', 'H.PAPClosure' and H.BCOClosure' all to this.
  | FunClosure

    -- | Unrecognized closure type
  | OtherClosure H.Closure
  deriving (Show)

getBoxedClosureData :: Box -> IO FlatClosure
getBoxedClosureData b = do
    tryForceBox b
    fromClosure =<< H.getBoxedClosureData b
  where
    fromClosure :: H.Closure -> IO FlatClosure
    fromClosure = \case
        -- Indirections
        --
        -- For background on black holes, see "Implementing Lazy Functional
        -- Languages on Stock Hardware: The Spineless Tagless G-machine", Simon
        -- Peyton Jones, Journal of Functional Programming, July 1992, section
        -- 9.3.3 "Black holes".

        H.BlackholeClosure _ x' -> getBoxedClosureData x'
        H.IndClosure       _ x' -> getBoxedClosureData x'

        -- Constructor application

        H.ConstrClosure{ptrArgs, pkg, modl, name} ->
          return $ ConstrClosure{..}

        -- Functions

        H.FunClosure{} -> return $ FunClosure
        H.PAPClosure{} -> return $ FunClosure
        H.BCOClosure{} -> return $ FunClosure

        -- Other kinds of constructors

        otherClosure ->
          return $ OtherClosure otherClosure

-- | Force the value to WHNF, if possible
--
-- We /cannot/ force the argument until we know what kind of closure we're
-- dealing with. If this is an unlifted closure, forcing it will result in a
-- ghc runtime crash.
tryForceBox :: Box -> IO ()
tryForceBox b@(Box x) = do
    closure <- H.getBoxedClosureData b
    case closure of

      H.APClosure{}       -> void $ evaluate x
      H.ThunkClosure{}    -> void $ evaluate x
      H.SelectorClosure{} -> void $ evaluate x
      _otherwise          -> return ()