[Git][ghc/ghc][wip/ipe-sharing] 7 commits: rts/CloneStack: Bounds check array write

Ben Gamari (@bgamari) gitlab at gitlab.haskell.org
Wed Sep 27 02:49:20 UTC 2023



Ben Gamari pushed to branch wip/ipe-sharing at Glasgow Haskell Compiler / GHC


Commits:
324f0917 by Ben Gamari at 2023-09-26T22:35:44-04:00
rts/CloneStack: Bounds check array write

- - - - -
9b150a97 by Ben Gamari at 2023-09-26T22:35:47-04:00
rts/CloneStack: Don't expose helper functions in header

- - - - -
cb6deb85 by Ben Gamari at 2023-09-26T22:35:47-04:00
base: Move internals of GHC.InfoProv into GHC.InfoProv.Types

Such that we can add new helpers into GHC.InfoProv.Types without
breakage.

- - - - -
b3612f68 by Ben Gamari at 2023-09-26T22:35:48-04:00
rts: Lazily decode IPE tables

Previously we would eagerly allocate `InfoTableEnt`s for each
info table registered in the info table provenance map. However, this
costs considerable memory and initialization time. Instead we now
lazily decode these tables. This allows us to use one-third the memory
*and* opens the door to taking advantage of sharing opportunities within
a module.

This required considerable reworking since lookupIPE now must be passed
its result buffer.

- - - - -
67077095 by Ben Gamari at 2023-09-26T22:35:48-04:00
rts/IPE: Share module_name within a Node

This allows us to shave a 64-bit word off of the packed IPE entry size.

- - - - -
5123017c by Ben Gamari at 2023-09-26T22:35:48-04:00
IPE: Include unit id

- - - - -
86d19016 by Ben Gamari at 2023-09-26T22:44:08-04:00
rts/IPE: Don't expose helper in header

- - - - -


22 changed files:

- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/StgToCmm/InfoTableProv.hs
- libraries/base/GHC/InfoProv.hsc → libraries/base/GHC/InfoProv.hs
- + libraries/base/GHC/InfoProv/Types.hsc
- libraries/base/GHC/Stack/CloneStack.hs
- libraries/base/base.cabal
- rts/CloneStack.c
- rts/CloneStack.h
- rts/IPE.c
- rts/IPE.h
- rts/PrimOps.cmm
- rts/Trace.c
- rts/include/rts/IPE.h
- testsuite/tests/interface-stability/base-exports.stdout
- testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
- testsuite/tests/interface-stability/base-exports.stdout-mingw32
- testsuite/tests/interface-stability/base-exports.stdout-ws-32
- testsuite/tests/profiling/should_run/staticcallstack001.stdout
- testsuite/tests/profiling/should_run/staticcallstack002.stdout
- testsuite/tests/rts/ipe/ipeEventLog_fromMap.c
- testsuite/tests/rts/ipe/ipeMap.c
- testsuite/tests/rts/ipe/ipe_lib.c


Changes:

=====================================
compiler/GHC/Builtin/primops.txt.pp
=====================================
@@ -3803,10 +3803,9 @@ primop  ClearCCSOp "clearCCS#" GenPrimOp
 section "Info Table Origin"
 ------------------------------------------------------------------------
 primop WhereFromOp "whereFrom#" GenPrimOp
-   a -> State# s -> (# State# s, Addr# #)
-   { Returns the @InfoProvEnt @ for the info table of the given object
-     (value is @NULL@ if the table does not exist or there is no information
-     about the closure).}
+   a -> Addr# -> State# s -> (# State# s, Int# #)
+   { Fills the given buffer with the @InfoProvEnt@ for the info table of the
+     given object. Returns @1#@ on success and @0#@ otherwise.}
    with
    out_of_line = True
 


=====================================
compiler/GHC/StgToCmm/InfoTableProv.hs
=====================================
@@ -83,9 +83,11 @@ emitIpeBufferListNode this_mod ents = do
         platform = stgToCmmPlatform cfg
         int n    = mkIntCLit platform n
 
-        (cg_ipes, strtab) = flip runState emptyStringTable $ do
-          module_name <- lookupStringTable $ ST.pack $ renderWithContext ctx (ppr this_mod)
-          mapM (toCgIPE platform ctx module_name) ents
+        ((cg_ipes, unit_id, module_name), strtab) = flip runState emptyStringTable $ do
+          unit_id <- lookupStringTable $ ST.pack $ renderWithContext ctx (ppr $ moduleName this_mod)
+          module_name <- lookupStringTable $ ST.pack $ renderWithContext ctx (ppr $ moduleUnit this_mod)
+          cg_ipes <- mapM (toCgIPE platform ctx) ents
+          return (cg_ipes, unit_id, module_name)
 
         tables :: [CmmStatic]
         tables = map (CmmStaticLit . CmmLabel . ipeInfoTablePtr) cg_ipes
@@ -136,6 +138,12 @@ emitIpeBufferListNode this_mod ents = do
 
             -- 'string_table_size' field (decompressed size)
           , int $ BS.length uncompressed_strings
+
+            -- 'module_name' field
+          , CmmInt (fromIntegral module_name) W32
+
+            -- 'unit_id' field
+          , CmmInt (fromIntegral unit_id) W32
           ]
 
     -- Emit the list of info table pointers
@@ -173,10 +181,8 @@ toIpeBufferEntries byte_order cg_ipes =
       , ipeClosureDesc cg_ipe
       , ipeTypeDesc cg_ipe
       , ipeLabel cg_ipe
-      , ipeModuleName cg_ipe
       , ipeSrcFile cg_ipe
       , ipeSrcSpan cg_ipe
-      , 0 -- padding
       ]
 
     word32Builder :: Word32 -> BSB.Builder
@@ -184,8 +190,8 @@ toIpeBufferEntries byte_order cg_ipes =
       BigEndian    -> BSB.word32BE
       LittleEndian -> BSB.word32LE
 
-toCgIPE :: Platform -> SDocContext -> StrTabOffset -> InfoProvEnt -> State StringTable CgInfoProvEnt
-toCgIPE platform ctx module_name ipe = do
+toCgIPE :: Platform -> SDocContext -> InfoProvEnt -> State StringTable CgInfoProvEnt
+toCgIPE platform ctx ipe = do
     table_name <- lookupStringTable $ ST.pack $ renderWithContext ctx (pprCLabel platform (infoTablePtr ipe))
     closure_desc <- lookupStringTable $ ST.pack $ show (infoProvEntClosureType ipe)
     type_desc <- lookupStringTable $ ST.pack $ infoTableType ipe
@@ -205,7 +211,6 @@ toCgIPE platform ctx module_name ipe = do
                            , ipeClosureDesc = closure_desc
                            , ipeTypeDesc = type_desc
                            , ipeLabel = label
-                           , ipeModuleName = module_name
                            , ipeSrcFile = src_file
                            , ipeSrcSpan = src_span
                            }
@@ -216,7 +221,6 @@ data CgInfoProvEnt = CgInfoProvEnt
                                , ipeClosureDesc :: !StrTabOffset
                                , ipeTypeDesc :: !StrTabOffset
                                , ipeLabel :: !StrTabOffset
-                               , ipeModuleName :: !StrTabOffset
                                , ipeSrcFile :: !StrTabOffset
                                , ipeSrcSpan :: !StrTabOffset
                                }


=====================================
libraries/base/GHC/InfoProv.hsc → libraries/base/GHC/InfoProv.hs
=====================================
@@ -26,72 +26,15 @@
 module GHC.InfoProv
     ( InfoProv(..)
     , ipLoc
-    , ipeProv
     , whereFrom
       -- * Internals
     , InfoProvEnt
+    , ipeProv
     , peekInfoProv
     ) where
 
-#include "Rts.h"
-
 import GHC.Base
-import GHC.Show
-import GHC.Ptr (Ptr(..), plusPtr, nullPtr)
-import GHC.Foreign (CString, peekCString)
-import GHC.IO.Encoding (utf8)
-import Foreign.Storable (peekByteOff)
-
-data InfoProv = InfoProv {
-  ipName :: String,
-  ipDesc :: String,
-  ipTyDesc :: String,
-  ipLabel :: String,
-  ipMod :: String,
-  ipSrcFile :: String,
-  ipSrcSpan :: String
-} deriving (Eq, Show)
-
-data InfoProvEnt
-
-ipLoc :: InfoProv -> String
-ipLoc ipe = ipSrcFile ipe ++ ":" ++ ipSrcSpan ipe
-
-getIPE :: a -> IO (Ptr InfoProvEnt)
-getIPE obj = IO $ \s ->
-   case whereFrom## obj s of
-     (## s', addr ##) -> (## s', Ptr addr ##)
-
-ipeProv :: Ptr InfoProvEnt -> Ptr InfoProv
-ipeProv p = (#ptr InfoProvEnt, prov) p
-
-peekIpName, peekIpDesc, peekIpLabel, peekIpModule, peekIpSrcFile, peekIpSrcSpan, peekIpTyDesc :: Ptr InfoProv -> IO CString
-peekIpName p    =  (# peek InfoProv, table_name) p
-peekIpDesc p    =  (# peek InfoProv, closure_desc) p
-peekIpLabel p   =  (# peek InfoProv, label) p
-peekIpModule p  =  (# peek InfoProv, module) p
-peekIpSrcFile p =  (# peek InfoProv, src_file) p
-peekIpSrcSpan p =  (# peek InfoProv, src_span) p
-peekIpTyDesc p  =  (# peek InfoProv, ty_desc) p
-
-peekInfoProv :: Ptr InfoProv -> IO InfoProv
-peekInfoProv infop = do
-  name <- peekCString utf8 =<< peekIpName infop
-  desc <- peekCString utf8 =<< peekIpDesc infop
-  tyDesc <- peekCString utf8 =<< peekIpTyDesc infop
-  label <- peekCString utf8 =<< peekIpLabel infop
-  mod <- peekCString utf8 =<< peekIpModule infop
-  file <- peekCString utf8 =<< peekIpSrcFile infop
-  span <- peekCString utf8 =<< peekIpSrcSpan infop
-  return InfoProv {
-      ipName = name,
-      ipDesc = desc,
-      ipTyDesc = tyDesc,
-      ipLabel = label,
-      ipMod = mod,
-      ipSrcFile = file,
-      ipSrcSpan = span
-    }
+import GHC.InfoProv.Types
 
 -- | Get information about where a value originated from.
 -- This information is stored statically in a binary when `-finfo-table-map` is
@@ -105,14 +48,5 @@ peekInfoProv infop = do
 --
 -- @since 4.16.0.0
 whereFrom :: a -> IO (Maybe InfoProv)
-whereFrom obj = do
-  ipe <- getIPE obj
-  -- The primop returns the null pointer in two situations at the moment
-  -- 1. The lookup fails for whatever reason
-  -- 2. -finfo-table-map is not enabled.
-  -- It would be good to distinguish between these two cases somehow.
-  if ipe == nullPtr
-    then return Nothing
-    else do
-      infoProv <- peekInfoProv (ipeProv ipe)
-      return $ Just infoProv
+whereFrom obj = getIPE obj Nothing $ \p ->
+    Just `fmap` peekInfoProv (ipeProv p)


=====================================
libraries/base/GHC/InfoProv/Types.hsc
=====================================
@@ -0,0 +1,95 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+#include "Rts.h"
+
+module GHC.InfoProv.Types
+    ( InfoProv(..)
+    , ipLoc
+    , ipeProv
+    , InfoProvEnt
+    , peekInfoProv
+    , getIPE
+    , StgInfoTable
+    , lookupIPE
+    ) where
+
+import GHC.Base
+import GHC.Show (Show)
+import GHC.Ptr (Ptr(..), plusPtr)
+import GHC.Foreign (CString, peekCString)
+import Foreign.C.Types (CBool(..))
+import Foreign.Marshal.Alloc (allocaBytes)
+import GHC.IO.Encoding (utf8)
+import Foreign.Storable (peekByteOff)
+
+data InfoProv = InfoProv {
+  ipName :: String,
+  ipDesc :: String,
+  ipTyDesc :: String,
+  ipLabel :: String,
+  ipUnitId :: String,
+  ipMod :: String,
+  ipSrcFile :: String,
+  ipSrcSpan :: String
+} deriving (Eq, Show)
+
+ipLoc :: InfoProv -> String
+ipLoc ipe = ipSrcFile ipe ++ ":" ++ ipSrcSpan ipe
+
+data InfoProvEnt
+
+data StgInfoTable
+
+foreign import ccall "lookupIPE" c_lookupIPE :: Ptr StgInfoTable -> Ptr InfoProv -> IO CBool
+
+lookupIPE :: Ptr StgInfoTable -> IO (Maybe InfoProv)
+lookupIPE itbl = allocaBytes (#size InfoProvEnt) $ \p -> do
+  res <- c_lookupIPE itbl p
+  case res of
+    1 -> Just `fmap` peekInfoProv p
+    _ -> return Nothing
+
+getIPE :: a -> r -> (Ptr InfoProvEnt -> IO r) -> IO r
+getIPE obj fail k = allocaBytes (#size InfoProvEnt) $ \p -> IO $ \s ->
+  case whereFrom## obj (unPtr p) s of
+    (## s', 1## ##) -> unIO (k p) s'
+    (## s', _   ##) -> (## s', fail ##)
+  where
+    unPtr (Ptr p) = p
+
+ipeProv :: Ptr InfoProvEnt -> Ptr InfoProv
+ipeProv p = (#ptr InfoProvEnt, prov) p
+
+peekIpName, peekIpDesc, peekIpLabel, peekIpUnitId, peekIpModule, peekIpSrcFile, peekIpSrcSpan, peekIpTyDesc :: Ptr InfoProv -> IO CString
+peekIpName p    =  (# peek InfoProv, table_name) p
+peekIpDesc p    =  (# peek InfoProv, closure_desc) p
+peekIpLabel p   =  (# peek InfoProv, label) p
+peekIpUnitId p  =  (# peek InfoProv, unit_id) p
+peekIpModule p  =  (# peek InfoProv, module) p
+peekIpSrcFile p =  (# peek InfoProv, src_file) p
+peekIpSrcSpan p =  (# peek InfoProv, src_span) p
+peekIpTyDesc p  =  (# peek InfoProv, ty_desc) p
+
+peekInfoProv :: Ptr InfoProv -> IO InfoProv
+peekInfoProv infop = do
+  name <- peekCString utf8 =<< peekIpName infop
+  desc <- peekCString utf8 =<< peekIpDesc infop
+  tyDesc <- peekCString utf8 =<< peekIpTyDesc infop
+  label <- peekCString utf8 =<< peekIpLabel infop
+  unit_id <- peekCString utf8 =<< peekIpUnitId infop
+  mod <- peekCString utf8 =<< peekIpModule infop
+  file <- peekCString utf8 =<< peekIpSrcFile infop
+  span <- peekCString utf8 =<< peekIpSrcSpan infop
+  return InfoProv {
+      ipName = name,
+      ipDesc = desc,
+      ipTyDesc = tyDesc,
+      ipLabel = label,
+      ipUnitId = unit_id,
+      ipMod = mod,
+      ipSrcFile = file,
+      ipSrcSpan = span
+    }


=====================================
libraries/base/GHC/Stack/CloneStack.hs
=====================================
@@ -27,8 +27,8 @@ import Data.Maybe (catMaybes)
 import Foreign
 import GHC.Conc.Sync
 import GHC.Exts (Int (I#), RealWorld, StackSnapshot#, ThreadId#, Array#, sizeofArray#, indexArray#, State#, StablePtr#)
-import GHC.IO (IO (..))
-import GHC.InfoProv (InfoProv (..), InfoProvEnt, ipLoc, ipeProv, peekInfoProv)
+import GHC.IO (IO (..), unIO, unsafeInterleaveIO)
+import GHC.InfoProv.Types (InfoProv (..), ipLoc, lookupIPE, StgInfoTable)
 import GHC.Stable
 
 -- | A frozen snapshot of the state of an execution stack.
@@ -36,7 +36,7 @@ import GHC.Stable
 -- @since 4.17.0.0
 data StackSnapshot = StackSnapshot !StackSnapshot#
 
-foreign import prim "stg_decodeStackzh" decodeStack# :: StackSnapshot# -> State# RealWorld -> (# State# RealWorld, Array# (Ptr InfoProvEnt) #)
+foreign import prim "stg_decodeStackzh" decodeStack# :: StackSnapshot# -> State# RealWorld -> (# State# RealWorld, Array# (Ptr StgInfoTable) #)
 
 foreign import prim "stg_cloneMyStackzh" cloneMyStack# :: State# RealWorld -> (# State# RealWorld, StackSnapshot# #)
 
@@ -228,37 +228,30 @@ data StackEntry = StackEntry
 --
 -- @since 4.17.0.0
 decode :: StackSnapshot -> IO [StackEntry]
-decode stackSnapshot = do
-    stackEntries <- getDecodedStackArray stackSnapshot
-    ipes <- mapM unmarshal stackEntries
-    return $ catMaybes ipes
-
-    where
-      unmarshal :: Ptr InfoProvEnt -> IO (Maybe StackEntry)
-      unmarshal ipe = if ipe == nullPtr then
-                          pure Nothing
-                       else do
-                          infoProv <- (peekInfoProv . ipeProv) ipe
-                          pure $ Just (toStackEntry infoProv)
-      toStackEntry :: InfoProv -> StackEntry
-      toStackEntry infoProv =
-        StackEntry
-        { functionName = ipLabel infoProv,
-          moduleName = ipMod infoProv,
-          srcLoc = ipLoc infoProv,
-          -- read looks dangerous, be we can trust that the closure type is always there.
-          closureType = read . ipDesc $ infoProv
-        }
-
-getDecodedStackArray :: StackSnapshot -> IO [Ptr InfoProvEnt]
+decode stackSnapshot = catMaybes <$> getDecodedStackArray stackSnapshot
+
+toStackEntry :: InfoProv -> StackEntry
+toStackEntry infoProv =
+  StackEntry
+  { functionName = ipLabel infoProv,
+    moduleName = ipMod infoProv,
+    srcLoc = ipLoc infoProv,
+    -- read looks dangerous, be we can trust that the closure type is always there.
+    closureType = read . ipDesc $ infoProv
+  }
+
+getDecodedStackArray :: StackSnapshot -> IO [Maybe StackEntry]
 getDecodedStackArray (StackSnapshot s) =
   IO $ \s0 -> case decodeStack# s s0 of
-    (# s1, a #) -> (# s1, (go a ((I# (sizeofArray# a)) - 1)) #)
+    (# s1, arr #) -> unIO (go arr (I# (sizeofArray# arr) - 1)) s1
   where
-    go :: Array# (Ptr InfoProvEnt) -> Int -> [Ptr InfoProvEnt]
-    go stack 0 = [stackEntryAt stack 0]
-    go stack i = (stackEntryAt stack i) : go stack (i - 1)
-
-    stackEntryAt :: Array# (Ptr InfoProvEnt) -> Int -> Ptr InfoProvEnt
+    go :: Array# (Ptr StgInfoTable) -> Int -> IO [Maybe StackEntry]
+    go _stack (-1) = return []
+    go stack i = do
+      infoProv <- lookupIPE (stackEntryAt stack i)
+      rest <- unsafeInterleaveIO $ go stack (i-1)
+      return ((toStackEntry `fmap` infoProv) : rest)
+
+    stackEntryAt :: Array# (Ptr StgInfoTable) -> Int -> Ptr StgInfoTable
     stackEntryAt stack (I# i) = case indexArray# stack i of
       (# se #) -> se


=====================================
libraries/base/base.cabal
=====================================
@@ -339,6 +339,7 @@ Library
         Data.Semigroup.Internal
         Data.Typeable.Internal
         Foreign.ForeignPtr.Imp
+        GHC.InfoProv.Types
         GHC.IO.Handle.Lock.Common
         GHC.IO.Handle.Lock.Flock
         GHC.IO.Handle.Lock.LinuxOFD


=====================================
rts/CloneStack.c
=====================================
@@ -24,6 +24,13 @@
 
 #include <string.h>
 
+
+static StgWord getStackFrameCount(StgStack* stack);
+static StgWord getStackChunkClosureCount(StgStack* stack);
+static void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack);
+static StgClosure* createPtrClosure(Capability* cap, const StgInfoTable* itbl);
+static StgMutArrPtrs* allocateMutableArray(StgWord size);
+
 static StgStack* cloneStackChunk(Capability* capability, const StgStack* stack)
 {
   StgWord spOffset = stack->sp - stack->stack;
@@ -173,28 +180,13 @@ void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack) {
     StgPtr spBottom = last_stack->stack + last_stack->stack_size;
     for (; sp < spBottom; sp += stack_frame_sizeW((StgClosure *)sp)) {
       const StgInfoTable* infoTable = get_itbl((StgClosure *)sp);
-
-      // Add the IPE that was looked up by lookupIPE() to the MutableArray#.
-      // The "Info Table Provernance Entry Map" (IPE) idea is to use a pointer
-      // (address) to the info table to lookup entries, this is fulfilled in
-      // non-"Tables Next to Code" builds.
-      // When "Tables Next to Code" is used, the assembly label of the info table
-      // is between the info table and it's code. There's no other label in the
-      // assembly code which could be used instead, thus lookupIPE() is actually
-      // called with the code pointer of the info table.
-      // (As long as it's used consistently, this doesn't really matter - IPE uses
-      // the pointer only to connect an info table to it's provenance entry in the
-      // IPE map.)
-#if defined(TABLES_NEXT_TO_CODE)
-      InfoProvEnt* ipe = lookupIPE((StgInfoTable*) infoTable->code);
-#else
-      InfoProvEnt* ipe = lookupIPE(infoTable);
-#endif
-      arr->payload[index] = createPtrClosure(cap, ipe);
-
+      arr->payload[index] = createPtrClosure(cap, infoTable);
       index++;
     }
 
+    // Ensure that we didn't overflow the result array
+    ASSERT(index-1 < arr->ptrs);
+
     // check whether the stack ends in an underflow frame
     StgUnderflowFrame *frame = (StgUnderflowFrame *) (last_stack->stack
       + last_stack->stack_size - sizeofW(StgUnderflowFrame));
@@ -206,11 +198,11 @@ void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack) {
   }
 }
 
-// Create a GHC.Ptr (Haskell constructor: `Ptr InfoProvEnt`) pointing to the
-// IPE.
-StgClosure* createPtrClosure(Capability *cap, InfoProvEnt* ipe) {
+// Create a GHC.Ptr (Haskell constructor: `Ptr StgInfoTable`) pointing to the
+// info table.
+StgClosure* createPtrClosure(Capability *cap, const StgInfoTable* itbl) {
   StgClosure *p = (StgClosure *) allocate(cap, CONSTR_sizeW(0,1));
   SET_HDR(p, &base_GHCziPtr_Ptr_con_info, CCS_SYSTEM);
-  p->payload[0] = (StgClosure*) ipe;
+  p->payload[0] = (StgClosure*) itbl;
   return TAG_CLOSURE(1, p);
 }


=====================================
rts/CloneStack.h
=====================================
@@ -23,10 +23,4 @@ StgMutArrPtrs* decodeClonedStack(Capability *cap, StgStack* stack);
 void handleCloneStackMessage(MessageCloneStack *msg);
 #endif
 
-StgWord getStackFrameCount(StgStack* stack);
-StgWord getStackChunkClosureCount(StgStack* stack);
-void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack);
-StgClosure* createPtrClosure(Capability* cap, InfoProvEnt* ipe);
-StgMutArrPtrs* allocateMutableArray(StgWord size);
-
 #include "EndPrivate.h"


=====================================
rts/IPE.c
=====================================
@@ -52,16 +52,23 @@ of InfoProvEnt are represented in IpeBufferEntry as 32-bit offsets into the
 string table. This allows us to halve the size of the buffer entries on
 64-bit machines while significantly reducing the number of needed
 relocations, reducing linking cost. Moreover, the code generator takes care
-to deduplicate strings when generating the string table. When we insert a
-set of IpeBufferEntrys into the IPE hash-map we convert them to InfoProvEnts,
-which contain proper string pointers.
+to deduplicate strings when generating the string table.
 
 Building the hash map is done lazily, i.e. on first lookup or traversal. For
 this all IPE lists of all IpeBufferListNode are traversed to insert all IPEs.
+This involves allocating a IpeMapEntry for each IPE entry, pointing to the
+entry's containing IpeBufferListNode and its index in that node.
+
+When the user looks up an IPE entry, we convert it to the user-facing
+InfoProvEnt representation.
 
-After the content of a IpeBufferListNode has been inserted, it's freed.
 */
 
+typedef struct {
+    IpeBufferListNode *node;
+    uint32_t idx;
+} IpeMapEntry;
+
 #if defined(THREADED_RTS)
 static Mutex ipeMapLock;
 #endif
@@ -71,6 +78,9 @@ static HashTable *ipeMap = NULL;
 // Accessed atomically
 static IpeBufferListNode *ipeBufferList = NULL;
 
+static void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode*);
+static void updateIpeMap(void);
+
 #if defined(THREADED_RTS)
 
 void initIpe(void) { initMutex(&ipeMapLock); }
@@ -85,18 +95,23 @@ void exitIpe(void) { }
 
 #endif // THREADED_RTS
 
-static InfoProvEnt ipeBufferEntryToIpe(const char *strings, const StgInfoTable *tbl, const IpeBufferEntry ent)
+static InfoProvEnt ipeBufferEntryToIpe(const IpeBufferListNode *node, uint32_t idx)
 {
+    CHECK(idx < node->count);
+    CHECK(!node->compressed);
+    const char *strings = node->string_table;
+    const IpeBufferEntry *ent = &node->entries[idx];
     return (InfoProvEnt) {
-            .info = tbl,
+            .info = node->tables[idx],
             .prov = {
-                .table_name = &strings[ent.table_name],
-                .closure_desc = &strings[ent.closure_desc],
-                .ty_desc = &strings[ent.ty_desc],
-                .label = &strings[ent.label],
-                .module = &strings[ent.module_name],
-                .src_file = &strings[ent.src_file],
-                .src_span = &strings[ent.src_span]
+                .table_name = &strings[ent->table_name],
+                .closure_desc = &strings[ent->closure_desc],
+                .ty_desc = &strings[ent->ty_desc],
+                .label = &strings[ent->label],
+                .unit_id = &strings[node->unit_id],
+                .module = &strings[node->module_name],
+                .src_file = &strings[ent->src_file],
+                .src_span = &strings[ent->src_span]
             }
     };
 }
@@ -105,29 +120,22 @@ static InfoProvEnt ipeBufferEntryToIpe(const char *strings, const StgInfoTable *
 #if defined(TRACING)
 static void traceIPEFromHashTable(void *data STG_UNUSED, StgWord key STG_UNUSED,
                                   const void *value) {
-    InfoProvEnt *ipe = (InfoProvEnt *)value;
-    traceIPE(ipe);
+    const IpeMapEntry *map_ent = (const IpeMapEntry *)value;
+    const InfoProvEnt ipe = ipeBufferEntryToIpe(map_ent->node, map_ent->idx);
+    traceIPE(&ipe);
 }
 
 void dumpIPEToEventLog(void) {
     // Dump pending entries
-    IpeBufferListNode *cursor = RELAXED_LOAD(&ipeBufferList);
-    while (cursor != NULL) {
-        IpeBufferEntry *entries;
-        const char *strings;
+    IpeBufferListNode *node = RELAXED_LOAD(&ipeBufferList);
+    while (node != NULL) {
+        decompressIPEBufferListNodeIfCompressed(node);
 
-        // Decompress if compressed
-        decompressIPEBufferListNodeIfCompressed(cursor, &entries, &strings);
-
-        for (uint32_t i = 0; i < cursor->count; i++) {
-            const InfoProvEnt ent = ipeBufferEntryToIpe(
-                strings,
-                cursor->tables[i],
-                entries[i]
-            );
+        for (uint32_t i = 0; i < node->count; i++) {
+            const InfoProvEnt ent = ipeBufferEntryToIpe(node, i);
             traceIPE(&ent);
         }
-        cursor = cursor->next;
+        node = node->next;
     }
 
     // Dump entries already in hashmap
@@ -168,9 +176,15 @@ void registerInfoProvList(IpeBufferListNode *node) {
     }
 }
 
-InfoProvEnt *lookupIPE(const StgInfoTable *info) {
+bool lookupIPE(const StgInfoTable *info, InfoProvEnt *out) {
     updateIpeMap();
-    return lookupHashTable(ipeMap, (StgWord)info);
+    IpeMapEntry *map_ent = (IpeMapEntry *) lookupHashTable(ipeMap, (StgWord)info);
+    if (map_ent) {
+        *out = ipeBufferEntryToIpe(map_ent->node, map_ent->idx);
+        return true;
+    } else {
+        return false;
+    }
 }
 
 void updateIpeMap(void) {
@@ -188,47 +202,40 @@ void updateIpeMap(void) {
     }
 
     while (pending != NULL) {
-        IpeBufferListNode *current_node = pending;
-        IpeBufferEntry *entries;
-        const char *strings;
+        IpeBufferListNode *node = pending;
 
         // Decompress if compressed
-        decompressIPEBufferListNodeIfCompressed(current_node, &entries, &strings);
-
-        // Convert the on-disk IPE buffer entry representation (IpeBufferEntry)
-        // into the runtime representation (InfoProvEnt)
-        InfoProvEnt *ip_ents = stgMallocBytes(
-            sizeof(InfoProvEnt) * current_node->count,
-            "updateIpeMap: ip_ents"
-        );
-        for (uint32_t i = 0; i < current_node->count; i++) {
-            const IpeBufferEntry ent = entries[i];
-            const StgInfoTable *tbl = current_node->tables[i];
-            ip_ents[i] = ipeBufferEntryToIpe(strings, tbl, ent);
-            insertHashTable(ipeMap, (StgWord) tbl, &ip_ents[i]);
+        decompressIPEBufferListNodeIfCompressed(node);
+
+        // Insert entries into ipeMap
+        IpeMapEntry *map_ents = stgMallocBytes(node->count * sizeof(IpeMapEntry), "updateIpeMap: ip_ents");
+        for (uint32_t i = 0; i < node->count; i++) {
+            const StgInfoTable *tbl = node->tables[i];
+            map_ents[i].node = node;
+            map_ents[i].idx = i;
+            insertHashTable(ipeMap, (StgWord) tbl, &map_ents[i]);
         }
 
-        pending = current_node->next;
+        pending = node->next;
     }
 
     RELEASE_LOCK(&ipeMapLock);
 }
 
 /* Decompress the IPE data and strings table referenced by an IPE buffer list
-node if it is compressed. No matter whether the data is compressed, the pointers
-referenced by the 'entries_dst' and 'string_table_dst' parameters will point at
-the decompressed IPE data and string table for the given node, respectively,
-upon return from this function.
-*/
-void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode *node, IpeBufferEntry **entries_dst, const char **string_table_dst) {
+ * node if it is compressed. After returning node->compressed with be 0 and the
+ * string_table and entries fields will have their uncompressed values.
+ */
+void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode *node) {
     if (node->compressed == 1) {
+        node->compressed = 0;
+
         // The IPE list buffer node indicates that the strings table and
         // entries list has been compressed. If zstd is not available, fail.
         // If zstd is available, decompress.
 #if HAVE_LIBZSTD == 0
         barf("An IPE buffer list node has been compressed, but the "
-             "decompression library (zstd) is not available."
-);
+             "decompression library (zstd) is not available.");
 #else
         size_t compressed_sz = ZSTD_findFrameCompressedSize(
             node->string_table,
@@ -244,7 +251,7 @@ void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode *node, IpeBufferE
             node->string_table,
             compressed_sz
         );
-        *string_table_dst = decompressed_strings;
+        node->string_table = (const char *) decompressed_strings;
 
         // Decompress the IPE data
         compressed_sz = ZSTD_findFrameCompressedSize(
@@ -261,12 +268,8 @@ void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode *node, IpeBufferE
             node->entries,
             compressed_sz
         );
-        *entries_dst = decompressed_entries;
+        node->entries = decompressed_entries;
 #endif // HAVE_LIBZSTD == 0
 
-    } else {
-        // Not compressed, no need to decompress
-        *entries_dst = node->entries;
-        *string_table_dst = node->string_table;
     }
 }


=====================================
rts/IPE.h
=====================================
@@ -14,9 +14,7 @@
 #include "BeginPrivate.h"
 
 void dumpIPEToEventLog(void);
-void updateIpeMap(void);
 void initIpe(void);
 void exitIpe(void);
-void decompressIPEBufferListNodeIfCompressed(IpeBufferListNode*, IpeBufferEntry**, const char**);
 
 #include "EndPrivate.h"


=====================================
rts/PrimOps.cmm
=====================================
@@ -2554,13 +2554,13 @@ stg_closureSizzezh (P_ clos)
     return (len);
 }
 
-stg_whereFromzh (P_ clos)
+stg_whereFromzh (P_ clos, W_ buf)
 {
-    P_ ipe;
+    W_ success;
     W_ info;
     info = GET_INFO(UNTAG(clos));
-    (ipe) = foreign "C" lookupIPE(info "ptr");
-    return (ipe);
+    (success) = foreign "C" lookupIPE(info, buf);
+    return (success);
 }
 
 /* -----------------------------------------------------------------------------


=====================================
rts/Trace.c
=====================================
@@ -689,9 +689,10 @@ void traceIPE(const InfoProvEnt *ipe)
         ACQUIRE_LOCK(&trace_utx);
 
         tracePreface();
-        debugBelch("IPE: table_name %s, closure_desc %s, ty_desc %s, label %s, module %s, srcloc %s:%s\n",
+        debugBelch("IPE: table_name %s, closure_desc %s, ty_desc %s, label %s, unit %s, module %s, srcloc %s:%s\n",
                    ipe->prov.table_name, ipe->prov.closure_desc, ipe->prov.ty_desc,
-                   ipe->prov.label, ipe->prov.module, ipe->prov.src_file, ipe->prov.src_span);
+                   ipe->prov.label, ipe->prov.unit_id, ipe->prov.module,
+                   ipe->prov.src_file, ipe->prov.src_span);
 
         RELEASE_LOCK(&trace_utx);
     } else


=====================================
rts/include/rts/IPE.h
=====================================
@@ -18,6 +18,7 @@ typedef struct InfoProv_ {
     const char *closure_desc;
     const char *ty_desc;
     const char *label;
+    const char *unit_id;
     const char *module;
     const char *src_file;
     const char *src_span;
@@ -56,10 +57,8 @@ typedef struct {
     StringIdx closure_desc;
     StringIdx ty_desc;
     StringIdx label;
-    StringIdx module_name;
     StringIdx src_file;
     StringIdx src_span;
-    uint32_t _padding;
 } IpeBufferEntry;
 
 GHC_STATIC_ASSERT(sizeof(IpeBufferEntry) % (WORD_SIZE_IN_BITS / 8) == 0, "sizeof(IpeBufferEntry) must be a multiple of the word size");
@@ -77,13 +76,18 @@ typedef struct IpeBufferListNode_ {
     // When TNTC is enabled, these will point to the entry code
     // not the info table itself.
     const StgInfoTable **tables;
-
     IpeBufferEntry *entries;
     StgWord entries_size; // decompressed size
 
     const char *string_table;
     StgWord string_table_size; // decompressed size
+
+    // Shared by all entries
+    StringIdx unit_id;
+    StringIdx module_name;
 } IpeBufferListNode;
 
 void registerInfoProvList(IpeBufferListNode *node);
-InfoProvEnt *lookupIPE(const StgInfoTable *info);
+
+// Returns true on success, initializes `out`.
+bool lookupIPE(const StgInfoTable *info, InfoProvEnt *out);


=====================================
testsuite/tests/interface-stability/base-exports.stdout
=====================================
@@ -4677,7 +4677,7 @@ module GHC.Base where
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
   when :: forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#
@@ -6728,7 +6728,7 @@ module GHC.Exts where
   void# :: (# #)
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#
@@ -7958,7 +7958,7 @@ module GHC.IORef where
 module GHC.InfoProv where
   -- Safety: Trustworthy
   type InfoProv :: *
-  data InfoProv = InfoProv {ipName :: GHC.Base.String, ipDesc :: GHC.Base.String, ipTyDesc :: GHC.Base.String, ipLabel :: GHC.Base.String, ipMod :: GHC.Base.String, ipSrcFile :: GHC.Base.String, ipSrcSpan :: GHC.Base.String}
+  data InfoProv = InfoProv {ipName :: GHC.Base.String, ipDesc :: GHC.Base.String, ipTyDesc :: GHC.Base.String, ipLabel :: GHC.Base.String, ipUnitId :: GHC.Base.String, ipMod :: GHC.Base.String, ipSrcFile :: GHC.Base.String, ipSrcSpan :: GHC.Base.String}
   type InfoProvEnt :: *
   data InfoProvEnt
   ipLoc :: InfoProv -> GHC.Base.String
@@ -12056,7 +12056,7 @@ instance GHC.Show.Show GHC.RTS.Flags.RTSFlags -- Defined in ‘GHC.RTS.Flags’
 instance GHC.Show.Show GHC.RTS.Flags.TickyFlags -- Defined in ‘GHC.RTS.Flags’
 instance GHC.Show.Show GHC.RTS.Flags.TraceFlags -- Defined in ‘GHC.RTS.Flags’
 instance GHC.Show.Show GHC.IOPort.IOPortException -- Defined in ‘GHC.IOPort’
-instance GHC.Show.Show GHC.InfoProv.InfoProv -- Defined in ‘GHC.InfoProv’
+instance GHC.Show.Show base-4.19.0.0:GHC.InfoProv.Types.InfoProv -- Defined in ‘base-4.19.0.0:GHC.InfoProv.Types’
 instance GHC.Show.Show GHC.Stack.CloneStack.StackEntry -- Defined in ‘GHC.Stack.CloneStack’
 instance GHC.Show.Show GHC.StaticPtr.StaticPtrInfo -- Defined in ‘GHC.StaticPtr’
 instance GHC.Show.Show GHC.Stats.GCDetails -- Defined in ‘GHC.Stats’
@@ -12246,7 +12246,7 @@ instance GHC.Classes.Eq GHC.IO.IOMode.IOMode -- Defined in ‘GHC.IO.IOMode’
 instance GHC.Classes.Eq GHC.RTS.Flags.IoSubSystem -- Defined in ‘GHC.RTS.Flags’
 instance forall i e. GHC.Classes.Eq (GHC.IOArray.IOArray i e) -- Defined in ‘GHC.IOArray’
 instance forall a. GHC.Classes.Eq (GHC.IOPort.IOPort a) -- Defined in ‘GHC.IOPort’
-instance GHC.Classes.Eq GHC.InfoProv.InfoProv -- Defined in ‘GHC.InfoProv’
+instance GHC.Classes.Eq base-4.19.0.0:GHC.InfoProv.Types.InfoProv -- Defined in ‘base-4.19.0.0:GHC.InfoProv.Types’
 instance GHC.Classes.Eq GHC.Num.Integer.Integer -- Defined in ‘GHC.Num.Integer’
 instance GHC.Classes.Eq GHC.Num.BigNat.BigNat -- Defined in ‘GHC.Num.BigNat’
 instance GHC.Classes.Eq GHC.Num.Natural.Natural -- Defined in ‘GHC.Num.Natural’


=====================================
testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
=====================================
@@ -4677,7 +4677,7 @@ module GHC.Base where
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
   when :: forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#
@@ -6697,7 +6697,7 @@ module GHC.Exts where
   void# :: (# #)
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#


=====================================
testsuite/tests/interface-stability/base-exports.stdout-mingw32
=====================================
@@ -4680,7 +4680,7 @@ module GHC.Base where
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
   when :: forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Addr# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#
@@ -6877,7 +6877,7 @@ module GHC.Exts where
   void# :: (# #)
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#


=====================================
testsuite/tests/interface-stability/base-exports.stdout-ws-32
=====================================
@@ -4677,7 +4677,7 @@ module GHC.Base where
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
   when :: forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#
@@ -6728,7 +6728,7 @@ module GHC.Exts where
   void# :: (# #)
   waitRead# :: forall d. Int# -> State# d -> State# d
   waitWrite# :: forall d. Int# -> State# d -> State# d
-  whereFrom# :: forall a d. a -> State# d -> (# State# d, Addr# #)
+  whereFrom# :: forall a d. a -> Addr# -> State# d -> (# State# d, Int# #)
   word16ToInt16# :: Word16# -> Int16#
   word16ToWord# :: Word16# -> Word#
   word2Double# :: Word# -> Double#


=====================================
testsuite/tests/profiling/should_run/staticcallstack001.stdout
=====================================
@@ -1,3 +1,3 @@
-Just (InfoProv {ipName = "D_Main_4_con_info", ipDesc = "2", ipTyDesc = "D", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "16:13-27"})
-Just (InfoProv {ipName = "D_Main_2_con_info", ipDesc = "2", ipTyDesc = "D", ipLabel = "caf", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "13:1-9"})
-Just (InfoProv {ipName = "sat_s11M_info", ipDesc = "15", ipTyDesc = "D", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "18:23-32"})
+Just (InfoProv {ipName = "D_Main_4_con_info", ipDesc = "2", ipTyDesc = "D", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "16:13-27"})
+Just (InfoProv {ipName = "D_Main_2_con_info", ipDesc = "2", ipTyDesc = "D", ipLabel = "caf", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "13:1-9"})
+Just (InfoProv {ipName = "sat_s13S_info", ipDesc = "15", ipTyDesc = "D", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack001.hs", ipSrcSpan = "18:23-32"})


=====================================
testsuite/tests/profiling/should_run/staticcallstack002.stdout
=====================================
@@ -1,4 +1,4 @@
-Just (InfoProv {ipName = "sat_s11p_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "10:23-39"})
-Just (InfoProv {ipName = "sat_s11F_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "11:23-42"})
-Just (InfoProv {ipName = "sat_s11V_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "12:23-46"})
-Just (InfoProv {ipName = "sat_s12b_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "13:23-44"})
+Just (InfoProv {ipName = "sat_s13u_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "10:23-39"})
+Just (InfoProv {ipName = "sat_s13O_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "11:23-42"})
+Just (InfoProv {ipName = "sat_s148_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "12:23-46"})
+Just (InfoProv {ipName = "sat_s14s_info", ipDesc = "15", ipTyDesc = "Any", ipLabel = "main", ipUnitId = "main", ipMod = "Main", ipSrcFile = "staticcallstack002.hs", ipSrcSpan = "13:23-44"})


=====================================
testsuite/tests/rts/ipe/ipeEventLog_fromMap.c
=====================================
@@ -19,7 +19,8 @@ int main(int argc, char *argv[]) {
     registerInfoProvList(list2);
 
     // Query an IPE to initialize the underlying hash map.
-    lookupIPE(list1->tables[0]);
+    InfoProvEnt ipe;
+    lookupIPE(list1->tables[0], &ipe);
 
     // Trace all IPE events.
     dumpIPEToEventLog();


=====================================
testsuite/tests/rts/ipe/ipeMap.c
=====================================
@@ -28,14 +28,19 @@ int main(int argc, char *argv[]) {
     hs_exit();
 }
 
+static InfoProvEnt lookupIPE_(const char *where, const StgInfoTable *itbl) {
+    InfoProvEnt ent;
+    if (!lookupIPE(itbl, &ent)) {
+        barf("%s: Expected to find IPE entry", where);
+    }
+    return ent;
+}
+
 void shouldFindNothingInAnEmptyIPEMap(Capability *cap) {
     HaskellObj fortyTwo = UNTAG_CLOSURE(rts_mkInt(cap, 42));
-
-    InfoProvEnt *result = lookupIPE(get_itbl(fortyTwo));
-
-    if (result != NULL) {
-        errorBelch("Found entry in an empty IPE map!");
-        exit(1);
+    InfoProvEnt ent;
+    if (lookupIPE(get_itbl(fortyTwo), &ent)) {
+        barf("Found entry in an empty IPE map!");
     }
 }
 
@@ -48,6 +53,9 @@ HaskellObj shouldFindOneIfItHasBeenRegistered(Capability *cap) {
     StringTable st;
     init_string_table(&st);
 
+    node->unit_id = add_string(&st, "unit-id");
+    node->module_name = add_string(&st, "TheModule");
+
     HaskellObj fortyTwo = UNTAG_CLOSURE(rts_mkInt(cap, 42));
     node->next = NULL;
     node->compressed = 0;
@@ -60,20 +68,16 @@ HaskellObj shouldFindOneIfItHasBeenRegistered(Capability *cap) {
 
     registerInfoProvList(node);
 
-    InfoProvEnt *result = lookupIPE(get_itbl(fortyTwo));
+    InfoProvEnt result = lookupIPE_("shouldFindOneIfItHasBeenRegistered", get_itbl(fortyTwo));
 
-    if (result == NULL) {
-        errorBelch("shouldFindOneIfItHasBeenRegistered: Found no entry in IPE map!");
-        exit(1);
-    }
-
-    assertStringsEqual(result->prov.table_name, "table_name_042");
-    assertStringsEqual(result->prov.closure_desc, "closure_desc_042");
-    assertStringsEqual(result->prov.ty_desc, "ty_desc_042");
-    assertStringsEqual(result->prov.label, "label_042");
-    assertStringsEqual(result->prov.module, "module_042");
-    assertStringsEqual(result->prov.src_file, "src_file_042");
-    assertStringsEqual(result->prov.src_span, "src_span_042");
+    assertStringsEqual(result.prov.table_name, "table_name_042");
+    assertStringsEqual(result.prov.closure_desc, "closure_desc_042");
+    assertStringsEqual(result.prov.ty_desc, "ty_desc_042");
+    assertStringsEqual(result.prov.label, "label_042");
+    assertStringsEqual(result.prov.unit_id, "unit-id");
+    assertStringsEqual(result.prov.module, "TheModule");
+    assertStringsEqual(result.prov.src_file, "src_file_042");
+    assertStringsEqual(result.prov.src_span, "src_span_042");
 
     return fortyTwo;
 }
@@ -88,6 +92,9 @@ void shouldFindTwoIfTwoHaveBeenRegistered(Capability *cap,
     StringTable st;
     init_string_table(&st);
 
+    node->unit_id = add_string(&st, "unit-id");
+    node->module_name = add_string(&st, "TheModule");
+
     HaskellObj twentyThree = UNTAG_CLOSURE(rts_mkInt8(cap, 23));
     node->next = NULL;
     node->compressed = 0;
@@ -100,22 +107,11 @@ void shouldFindTwoIfTwoHaveBeenRegistered(Capability *cap,
 
     registerInfoProvList(node);
 
-    InfoProvEnt *resultFortyTwo =
-      lookupIPE(get_itbl(fortyTwo));
-    InfoProvEnt *resultTwentyThree =
-      lookupIPE(get_itbl(twentyThree));
+    InfoProvEnt resultFortyTwo = lookupIPE_("shouldFindTwoIfTwoHaveBeenRegistered", get_itbl(fortyTwo));
+    assertStringsEqual(resultFortyTwo.prov.table_name, "table_name_042");
 
-    if (resultFortyTwo == NULL) {
-        errorBelch("shouldFindTwoIfTwoHaveBeenRegistered(42): Found no entry in IPE map!");
-        exit(1);
-    }
-    if (resultTwentyThree == NULL) {
-        errorBelch("shouldFindTwoIfTwoHaveBeenRegistered(23): Found no entry in IPE map!");
-        exit(1);
-    }
-
-    assertStringsEqual(resultFortyTwo->prov.table_name, "table_name_042");
-    assertStringsEqual(resultTwentyThree->prov.table_name, "table_name_023");
+    InfoProvEnt resultTwentyThree = lookupIPE_("shouldFindTwoIfTwoHaveBeenRegistered", get_itbl(twentyThree));
+    assertStringsEqual(resultTwentyThree.prov.table_name, "table_name_023");
 }
 
 void shouldFindTwoFromTheSameList(Capability *cap) {
@@ -142,20 +138,11 @@ void shouldFindTwoFromTheSameList(Capability *cap) {
 
     registerInfoProvList(node);
 
-    InfoProvEnt *resultOne = lookupIPE(get_itbl(one));
-    InfoProvEnt *resultTwo = lookupIPE(get_itbl(two));
-
-    if (resultOne == NULL) {
-        errorBelch("shouldFindTwoFromTheSameList(1): Found no entry in IPE map!");
-        exit(1);
-    }
-    if (resultTwo == NULL) {
-        errorBelch("shouldFindTwoFromTheSameList(2): Found no entry in IPE map!");
-        exit(1);
-    }
+    InfoProvEnt resultOne = lookupIPE_("shouldFindTwoFromTheSameList", get_itbl(one));
+    assertStringsEqual(resultOne.prov.table_name, "table_name_001");
 
-    assertStringsEqual(resultOne->prov.table_name, "table_name_001");
-    assertStringsEqual(resultTwo->prov.table_name, "table_name_002");
+    InfoProvEnt resultTwo = lookupIPE_("shouldFindTwoFromTheSameList", get_itbl(two));
+    assertStringsEqual(resultTwo.prov.table_name, "table_name_002");
 }
 
 void shouldDealWithAnEmptyList(Capability *cap, HaskellObj fortyTwo) {
@@ -166,15 +153,8 @@ void shouldDealWithAnEmptyList(Capability *cap, HaskellObj fortyTwo) {
 
     registerInfoProvList(node);
 
-    InfoProvEnt *resultFortyTwo =
-        lookupIPE(get_itbl(fortyTwo));
-
-    if (resultFortyTwo == NULL) {
-        errorBelch("shouldDealWithAnEmptyList: Found no entry in IPE map!");
-        exit(1);
-    }
-
-    assertStringsEqual(resultFortyTwo->prov.table_name, "table_name_042");
+    InfoProvEnt resultFortyTwo = lookupIPE_("shouldDealWithAnEmptyList", get_itbl(fortyTwo));
+    assertStringsEqual(resultFortyTwo.prov.table_name, "table_name_042");
 }
 
 void assertStringsEqual(const char *s1, const char *s2) {


=====================================
testsuite/tests/rts/ipe/ipe_lib.c
=====================================
@@ -48,11 +48,6 @@ IpeBufferEntry makeAnyProvEntry(Capability *cap, StringTable *st, int i) {
     snprintf(label, labelLength, "label_%03i", i);
     provEnt.label = add_string(st, label);
 
-    unsigned int moduleLength = strlen("module_") + 3 /* digits */ + 1 /* null character */;
-    char *module = malloc(sizeof(char) * moduleLength);
-    snprintf(module, moduleLength, "module_%03i", i);
-    provEnt.module_name = add_string(st, module);
-
     unsigned int srcFileLength = strlen("src_file_") + 3 /* digits */ + 1 /* null character */;
     char *srcFile = malloc(sizeof(char) * srcFileLength);
     snprintf(srcFile, srcFileLength, "src_file_%03i", i);
@@ -77,6 +72,16 @@ IpeBufferListNode *makeAnyProvEntries(Capability *cap, int start, int end) {
     StringTable st;
     init_string_table(&st);
 
+    unsigned int unitIdLength = strlen("unit_id_") + 3 /* digits */ + 1 /* null character */;
+    char *unitId = malloc(sizeof(char) * unitIdLength);
+    snprintf(unitId, unitIdLength, "unit_id_%03i", start);
+    node->unit_id = add_string(&st, unitId);
+
+    unsigned int moduleLength = strlen("module_") + 3 /* digits */ + 1 /* null character */;
+    char *module = malloc(sizeof(char) * moduleLength);
+    snprintf(module, moduleLength, "module_%03i", start);
+    node->module_name = add_string(&st, module);
+
     // Make the entries and fill the buffers
     for (int i=start; i < end; i++) {
         HaskellObj closure = rts_mkInt(cap, 42);



View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d0e5d6f27b1fa52e9dccc3b2fdc6bf135e6c7bd8...86d19016700222e72b6e2172f9f6f991aec93d32

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d0e5d6f27b1fa52e9dccc3b2fdc6bf135e6c7bd8...86d19016700222e72b6e2172f9f6f991aec93d32
You're receiving this email because of your account on gitlab.haskell.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20230926/8e170c6c/attachment-0001.html>


More information about the ghc-commits mailing list