packages feed

ghc-debug-common-0.7.0.0: src/GHC/Debug/Decode.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE GHCForeignImportPrim #-}
{-# LANGUAGE UnliftedFFITypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Low-level functions for decoding a closure representation from the raw
-- bytes
module GHC.Debug.Decode ( decodeClosure
                        , decodeInfoTable
                        , decodeCCS
                        , decodeIndexTable
                        ) where

 -- (Addr#, unsafeCoerce#, Any, Word#, ByteArray#)
import GHC.Word

import qualified Data.ByteString.Lazy as BSL

import GHC.Debug.Types.Ptr
import GHC.Debug.Types.Version
import GHC.Debug.Types.Closures
import Data.Binary.Get as B
import Data.Binary
import Control.Monad
import Data.Bits
import Data.Functor
import GHC.Debug.Types (getCCS, getIndexTable)
import qualified Data.Array as A
import GHC.Stack

decodeClosureHeader :: Version -> Get (Maybe ProfHeaderWithPtr)
decodeClosureHeader ver = do
  () <$ skip (8 * 1)
  getProfHeader ver

getProfHeader :: Version -> Get (Maybe ProfHeaderWithPtr)
getProfHeader ver =
  case v_profiling ver of
    Nothing -> pure Nothing
    Just mode -> do
      ccs <- get
      header <- getWord64le
      pure $ Just $ ProfHeader ccs (decodeHeader mode header)

decodeHeader :: ProfilingMode -> Word64 -> ProfHeaderWord
decodeHeader mode hp = case mode of
  NoProfiling -> OtherHeader hp
  OtherProfiling -> OtherHeader hp
  -- TODO handle 32 bit
  RetainerProfiling -> RetainerHeader (testBit hp 0) (RetainerSetPtr $ clearBit hp 0)
  LDVProfiling -> LDVWord (testBit hp 60) (fromIntegral ((hp .&. _LDV_CREATE_MASK) `shiftR` _LDV_SHIFT)) (fromIntegral (hp .&. _LDV_LAST_MASK))
  EraProfiling -> EraWord hp

_LDV_CREATE_MASK, _LDV_LAST_MASK :: Word64
_LDV_CREATE_MASK = 0x0FFFFFFFC0000000
_LDV_LAST_MASK = 0x000000003FFFFFFF
_LDV_SHIFT :: Int
_LDV_SHIFT = 30

decodePAPClosure :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodePAPClosure ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  carity <- getWord32le
  nargs <- getWord32le
  funp <- getClosurePtr
  cpayload <- replicateM (fromIntegral nargs) getWord64le
  let cont = PayloadCont funp cpayload
  return $ (GHC.Debug.Types.Closures.PAPClosure infot prof carity nargs funp cont)

decodeAPClosure :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeAPClosure ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  _smp_header <- getWord64le
  -- _itbl <- decodeClosureHeader
  carity <- getWord32le
  nargs <- getWord32le
  funp <- getClosurePtr
  cpayload <- replicateM (fromIntegral nargs) getWord64le
  let cont = PayloadCont funp cpayload
  return $ (GHC.Debug.Types.Closures.APClosure infot prof carity nargs funp cont)


decodeTVarClosure :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeTVarClosure ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  ptr <- getClosurePtr
  watch_queue <- getClosurePtr
  updates <- getInt64le
  return $ (TVarClosure infot prof ptr watch_queue (fromIntegral updates))

getClosurePtr :: Get ClosurePtr
getClosurePtr = get

getWord :: Get Word64
getWord = getWord64le

decodeMutPrim :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeMutPrim ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  let kptrs = fromIntegral (ptrs (decodedTable infot))
      kdat = fromIntegral (nptrs (decodedTable infot))
  pts <- replicateM kptrs getClosurePtr
  dat <- replicateM kdat (fromIntegral <$> getWord64le)
  return $ (MutPrimClosure infot prof pts dat)

decodePrim :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodePrim ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  let kptrs = fromIntegral (ptrs (decodedTable infot))
      kdat = fromIntegral (nptrs (decodedTable infot))
  pts <- replicateM kptrs getClosurePtr
  dat <- replicateM kdat (fromIntegral <$> getWord64le)
  return $ (PrimClosure infot prof pts dat)

decodeTrecChunk :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeTrecChunk ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  prev <- getClosurePtr
  clos_next_idx <- getWord64le
  chunks <- replicateM (fromIntegral clos_next_idx) getChunk
  return $ (TRecChunkClosure infot prof prev (fromIntegral clos_next_idx) chunks)
  where
    getChunk = do
      TRecEntry <$> getClosurePtr
                <*> getClosurePtr
                <*> getClosurePtr
                <*> (fromIntegral <$> getInt64le) -- TODO: num_updates field is wrong
                                                  -- Not sure how it should
                                                  -- be decoded

decodeBlockingQueue :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeBlockingQueue ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  q <- getClosurePtr
  bh <- getClosurePtr
  tso <- getClosurePtr
  bh_q <- getClosurePtr
  return $ (GHC.Debug.Types.Closures.BlockingQueueClosure infot prof q bh tso bh_q)

-- It is just far simpler to directly decode the stack here rather than use
-- the existing logic in ghc-heap......
decodeStack :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeStack ver (infot, _) (cp, rc) = decodeFromBS rc $ do
   prof <- decodeClosureHeader ver
   st_size <- getWord32le
   st_dirty <- getWord8
   st_marking <- getWord8
   -- Up to now, 14 bytes are read, skip 2 to get to 16/start of
   -- sp field
   skip 2
   st_sp <- StackPtr <$> getWord
   stackHeaderSize <- bytesRead
   let stack_offset = fromIntegral (subtractStackPtr st_sp cp)
             -- -stackHeaderSize for the bytes already read
             - fromIntegral stackHeaderSize
       len = calculateStackLen st_size (fromIntegral stackHeaderSize) cp st_sp
   -- Skip to start of stack frames
   skip stack_offset
   -- Read the raw frames, we can't decode them yet because we
   -- need to query the debuggee for the bitmaps
   raw_stack <- RawStack <$> getByteString (fromIntegral len)
   return (GHC.Debug.Types.Closures.StackClosure
            infot
            prof
            st_size
            st_dirty
            st_marking
            (StackCont st_sp raw_stack))

decodeFromBS :: HasCallStack => RawClosure -> Get (DebugClosure ccs srt pap string s b)
                           -> DebugClosureWithExtra Size ccs srt pap string s b
decodeFromBS (RawClosure rc) parser =
  case runGetOrFail parser (BSL.fromStrict rc) of
    Left err -> error ("DEC:" ++ show err ++ printBS rc)
    Right (_rem, o, v) ->
      let !s = fromIntegral o
      in DCS (Size s) v

decodeFromBS' :: RawClosure -> Get a -> a
decodeFromBS' (RawClosure rc) parser =
  case runGetOrFail parser (BSL.fromStrict rc) of
    Left err -> error ("DEC:" ++ show err ++ printBS rc)
    Right (_rem, _offset, v) -> v

decodeAPStack :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeAPStack ver (infot, _) (ClosurePtr cp, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  _smp_header <- getWord64le
  st_size <- getWord
  fun_closure <- getClosurePtr
  k <- bytesRead
  let sp = addStackPtr (StackPtr cp) (fromIntegral k)
  clos_payload <- RawStack <$> getByteString (fromIntegral st_size)
  return $ GHC.Debug.Types.Closures.APStackClosure
              infot
              prof
              (fromIntegral st_size)
              fun_closure
              (StackCont sp clos_payload)

decodeStandardLayout :: Version
                     -> Get ()
                     -> (Maybe ProfHeaderWithPtr -> [ClosurePtr] -> [Word] -> Closure)
                     -> (StgInfoTableWithPtr, RawInfoTable)
                     -> (ClosurePtr, RawClosure)
                     -> SizedClosure
decodeStandardLayout ver extra k (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  -- For the THUNK header
  extra
  pts <- replicateM (fromIntegral (ptrs (decodedTable infot))) getClosurePtr
  cwords <- replicateM (fromIntegral (nptrs (decodedTable infot))) getWord
  return $ k prof pts (map fromIntegral cwords)

decodeArrWords :: Version -> (StgInfoTableWithPtr, b)
               -> (a, RawClosure) -> SizedClosure
decodeArrWords ver  (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  bytes <- getWord64le
  payload <- replicateM (fromIntegral $ bytes `ceilIntDiv` 8) getWord
  return $ GHC.Debug.Types.Closures.ArrWordsClosure infot prof (fromIntegral bytes) (map fromIntegral payload)

-- | Compute @ceiling (a/b)@.
ceilIntDiv :: Integral a => a -> a -> a
ceilIntDiv a b = (a + b - 1) `div` b

tsoVersionChanged :: Version -> Bool
tsoVersionChanged (Version majv minv _ _) = (majv > 905) || (majv == 905 && minv >= 20220925)

whyBlockedWord32 :: Version -> Bool
whyBlockedWord32 (Version majv minv _ _) = majv > 909 || (majv == 909 && minv >= 20240201)

decodeTSO :: Version
          -> (StgInfoTableWithPtr, RawInfoTable)
          -> (a, RawClosure)
          -> SizedClosure
decodeTSO ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  link <- getClosurePtr
  global_link <- getClosurePtr
  tsoStack <- getClosurePtr
  what_next <- parseWhatNext <$> getWord16le
  (why_blocked, flags) <-
    if whyBlockedWord32 ver
      then do
        flags <- parseTsoFlags <$> getWord32le
        -- Padding
        skip 2
        why_blocked <- parseWhyBlocked <$> getWord32le
        -- Padding
        skip 4
        return (why_blocked, flags)
      else do
        why_blocked <- parseWhyBlocked . fromIntegral @Word16 @Word32 <$> getWord16le
        flags <- parseTsoFlags <$> getWord32le
        return (why_blocked, flags)
  _block_info <- getClosurePtr
  threadId <- getWord64le
  saved_errno <- getWord32le
  dirty       <- getWord32le

  _bound       <- getClosurePtr
  _cap         <- getClosurePtr
  trec           <- getClosurePtr
  threadLabel <-
    if tsoVersionChanged ver
      then do
        thread_label <- getClosurePtr
        return $ if thread_label == mkClosurePtr 0 then Nothing else Just thread_label
      else return Nothing
  blocked_exceptions <- getClosurePtr
  bq             <- getClosurePtr
  alloc_limit    <- getInt64le
  tot_stack_size <- getWord32le
  let res :: Closure = (GHC.Debug.Types.Closures.TSOClosure
            { info = infot
            , profHeader = prof
            , _link = link
            , prof = Nothing
            , .. })
  return res

parseWhatNext :: Word16 -> WhatNext
parseWhatNext i = case i of
  1 -> ThreadRunGHC
  2 -> ThreadInterpret
  3 -> ThreadKilled
  4 -> ThreadComplete
  _ -> WhatNextUnknownValue i

parseWhyBlocked :: Word32 -> WhyBlocked
parseWhyBlocked i = case i of
  0  -> NotBlocked
  1  -> BlockedOnMVar
  14 -> BlockedOnMVarRead
  2  -> BlockedOnBlackHole
  3  -> BlockedOnRead
  4  -> BlockedOnWrite
  5  -> BlockedOnDelay
  6  -> BlockedOnSTM
  7  -> BlockedOnDoProc
  10 -> BlockedOnCCall
  11 -> BlockedOnCCall_Interruptible
  12 -> BlockedOnMsgThrowTo
  13 -> ThreadMigrating
  _  -> WhyBlockedUnknownValue i

parseTsoFlags :: Word32 -> [TsoFlags]
parseTsoFlags w =
  go [ (TsoLocked             , 1)
     , (TsoBlockx             , 2)
     , (TsoInterruptible      , 3)
     , (TsoStoppedOnBreakpoint, 4)
     , (TsoMarked             , 5)
     , (TsoSqueezed           , 6)
     , (TsoAllocLimit         , 7)
     ]
  where
    go xs = [flag | (flag, i) <- xs, testBit w i]

decodeClosure :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeClosure ver i@(itb, _) c
  = case tipe (decodedTable itb) of
      ARR_WORDS -> decodeArrWords ver i c
      PAP -> decodePAPClosure ver i c
      AP -> decodeAPClosure ver i c
      TVAR -> decodeTVarClosure ver i c
      MUT_PRIM -> decodeMutPrim ver i c
      PRIM -> decodePrim ver i c
      TREC_CHUNK -> decodeTrecChunk ver i c
      BLOCKING_QUEUE -> decodeBlockingQueue ver i c
      TSO -> decodeTSO ver i c
      STACK -> decodeStack ver i c
      AP_STACK -> decodeAPStack ver i c
      THUNK_STATIC -> decodeStandardLayout ver (return ()) (\ph -> ThunkClosure itb ph (tableId itb)) i c
      THUNK_SELECTOR -> decodeThunkSelector ver i c
      BCO -> decodeBCO ver i c
      IND        -> decodeIndirectee ver IndClosure i c
      IND_STATIC -> decodeIndirectee ver IndClosure i c
      BLACKHOLE  -> decodeIndirectee ver BlackholeClosure i c
      MVAR_CLEAN -> decodeMVar ver i c
      MVAR_DIRTY -> decodeMVar ver i c
      MUT_VAR_CLEAN -> decodeMutVar ver i c
      MUT_VAR_DIRTY -> decodeMutVar ver i c
      WEAK -> decodeWeakClosure ver i c
      ty
        | CONSTR <= ty && ty <= CONSTR_0_2 ->
            decodeStandardLayout ver (return ()) (\ph pts ws -> ConstrClosure itb ph pts ws (tableId itb)) i c
        | CONSTR <= ty && ty <= CONSTR_NOCAF ->
            decodeStandardLayout ver (return ()) (\ph pts ws -> ConstrClosure itb ph pts ws (tableId itb)) i c
        | FUN <= ty && ty <= FUN_STATIC ->
            decodeStandardLayout ver (return ()) (\ph -> FunClosure itb ph (tableId itb)) i c
        | THUNK <= ty && ty <= THUNK_0_2 ->
            decodeStandardLayout ver (() <$ getWord) (\ph -> ThunkClosure itb ph (tableId itb)) i c
        | MUT_ARR_PTRS_CLEAN <= ty && ty <= MUT_ARR_PTRS_FROZEN_CLEAN ->
            decodeMutArr ver i c
        | SMALL_MUT_ARR_PTRS_CLEAN <= ty && ty <= SMALL_MUT_ARR_PTRS_FROZEN_CLEAN ->
            decodeSmallMutArr ver i c
        | otherwise -> error $ "unhandled closure type" ++ show ty

decodeCCS :: Version -> RawClosure -> CCSPayload
decodeCCS _ rc = decodeFromBS' rc getCCS

decodeIndexTable :: Version -> RawClosure -> IndexTable
decodeIndexTable _ rc = decodeFromBS' rc getIndexTable

decodeWeakClosure :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeWeakClosure ver (infot, _) (_, rc) = decodeFromBS rc $ do
  profHeader <- decodeClosureHeader ver
  cfinalizers <- getClosurePtr
  key <- getClosurePtr
  value <- getClosurePtr
  finalizer <- getClosurePtr
  mlink <- do
    p@(ClosurePtr w) <- getClosurePtr
    pure $ if w == 0 then Nothing else Just p
  pure $ WeakClosure infot profHeader cfinalizers key value finalizer mlink

decodeMVar :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeMVar ver (infot, _) (_, rc) = decodeFromBS rc $ do
  profHeader <- decodeClosureHeader ver
  hd <- getClosurePtr
  tl <- getClosurePtr
  val <- getClosurePtr
  pure $ MVarClosure infot profHeader hd tl val

decodeMutVar :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeMutVar ver (infot, _) (_, rc) = decodeFromBS rc $ do
  profHeader <- decodeClosureHeader ver
  val <- getClosurePtr
  pure $ MutVarClosure infot profHeader val

decodeMutArr :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeMutArr ver (infot, _) (_, rc) = decodeFromBS rc $ do
  profHeader <- decodeClosureHeader ver
  nptrs <- getWord64le
  size <- getWord64le
  payload <- replicateM (fromIntegral nptrs) getClosurePtr
  pure $ MutArrClosure infot profHeader (fromIntegral nptrs) (fromIntegral size) payload

decodeSmallMutArr :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeSmallMutArr ver (infot, _) (_, rc) = decodeFromBS rc $ do
  profHeader <- decodeClosureHeader ver
  nptrs <- getWord64le
  payload <- replicateM (fromIntegral nptrs) getClosurePtr
  pure $ SmallMutArrClosure infot profHeader (fromIntegral nptrs) payload

decodeIndirectee :: Version
                 -> (StgInfoTableWithPtr -> Maybe ProfHeaderWithPtr -> ClosurePtr -> Closure)
                 -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) -> SizedClosure
decodeIndirectee ver mk (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  ind <- getClosurePtr
  pure $ mk infot prof ind

decodeBCO :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeBCO ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  instrs <- getClosurePtr
  literals <- getClosurePtr
  bcoptrs <- getClosurePtr
  arity <- getWord32le
  size <- getWord32le
  bitmap <- decodePtrBitmap
  pure (BCOClosure infot prof instrs literals bcoptrs arity size bitmap)

  where
    unpackWord64 :: Word64 -> [Bool]
    unpackWord64 w = [ testBit w i | i <- [0..63] ]

    decodePtrBitmap :: Get PtrBitmap
    decodePtrBitmap = do
      size <- getWord64le
      let nWords = fromIntegral ((size + 63) `div` 64)
      bm_words <- replicateM nWords getWord64le
      let bits = take (fromIntegral size) (concatMap unpackWord64 bm_words)
          arr  = A.listArray (0, length bits - 1) bits
      return (PtrBitmap arr)



decodeThunkSelector :: Version -> (StgInfoTableWithPtr, RawInfoTable) -> (ClosurePtr, RawClosure) ->  SizedClosure
decodeThunkSelector ver (infot, _) (_, rc) = decodeFromBS rc $ do
  prof <- decodeClosureHeader ver
  (() <$ getWord)
  selectee <- getClosurePtr
  pure (SelectorClosure infot prof selectee)

decodeInfoTable :: Version -> RawInfoTable -> StgInfoTable
decodeInfoTable ver@Version{..} (RawInfoTable itbl) =
  case runGetOrFail itParser (BSL.fromStrict itbl) of
    Left err -> error ("DEC:" ++ show err ++ printBS itbl)
    Right (_rem, !_, v) -> v
  where
    itParser = do
      _entry <- case v_tntc of
        True -> pure Nothing
        False -> do
          getWord64le -- todo return funptr
          pure Nothing
      when (isProfiledRTS ver) $ do
        () <$ getWord64le
        () <$ getWord64le
      ptrs <- getWord32le
      nptrs <- getWord32le
      tipe <- getWord32le
      srtlen <- getWord32le
      return $
        StgInfoTable
        { ptrs = ptrs
        , nptrs = nptrs
        , tipe = decodeInfoTableType tipe
        , srtlen = srtlen
        }

decodeInfoTableType :: Word32 -> ClosureType
decodeInfoTableType i = case i of
  0 -> INVALID_OBJECT
  1 -> CONSTR
  2 -> CONSTR_1_0
  3 -> CONSTR_0_1
  4 -> CONSTR_2_0
  5 -> CONSTR_1_1
  6 -> CONSTR_0_2
  7 -> CONSTR_NOCAF
  8 -> FUN
  9 -> FUN_1_0
  10 -> FUN_0_1
  11 -> FUN_2_0
  12 -> FUN_1_1
  13 -> FUN_0_2
  14 -> FUN_STATIC
  15 -> THUNK
  16 -> THUNK_1_0
  17 -> THUNK_0_1
  18 -> THUNK_2_0
  19 -> THUNK_1_1
  20 -> THUNK_0_2
  21 -> THUNK_STATIC
  22 -> THUNK_SELECTOR
  23 -> BCO
  24 -> AP
  25 -> PAP
  26 -> AP_STACK
  27 -> IND
  28 -> IND_STATIC
  29 -> RET_BCO
  30 -> RET_SMALL
  31 -> RET_BIG
  32 -> RET_FUN
  33 -> UPDATE_FRAME
  34 -> CATCH_FRAME
  35 -> UNDERFLOW_FRAME
  36 -> STOP_FRAME
  37 -> BLOCKING_QUEUE
  38 -> BLACKHOLE
  39 -> MVAR_CLEAN
  40 -> MVAR_DIRTY
  41 -> TVAR
  42 -> ARR_WORDS
  43 -> MUT_ARR_PTRS_CLEAN
  44 -> MUT_ARR_PTRS_DIRTY
  45 -> MUT_ARR_PTRS_FROZEN_DIRTY
  46 -> MUT_ARR_PTRS_FROZEN_CLEAN
  47 -> MUT_VAR_CLEAN
  48 -> MUT_VAR_DIRTY
  49 -> WEAK
  50 -> PRIM
  51 -> MUT_PRIM
  52 -> TSO
  53 -> STACK
  54 -> TREC_CHUNK
  55 -> ATOMICALLY_FRAME
  56 -> CATCH_RETRY_FRAME
  57 -> CATCH_STM_FRAME
  58 -> WHITEHOLE
  59 -> SMALL_MUT_ARR_PTRS_CLEAN
  60 -> SMALL_MUT_ARR_PTRS_DIRTY
  61 -> SMALL_MUT_ARR_PTRS_FROZEN_DIRTY
  62 -> SMALL_MUT_ARR_PTRS_FROZEN_CLEAN
  63 -> COMPACT_NFDATA
  64 -> CONTINUATION
  65 -> N_CLOSURE_TYPES
  n  -> error $ "Unexpected closure type: " ++ show n