[Git][ghc/ghc][wip/ipe-sharing] 5 commits: rts: Lazily decode IPE tables

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



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


Commits:
b290ba2b by Ben Gamari at 2023-09-27T12:18:34-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.

- - - - -
d9481637 by Ben Gamari at 2023-09-27T12:18:34-04:00
rts/IPE: Don't expose helper in header

- - - - -
c53c4b3e by Ben Gamari at 2023-09-27T12:18:34-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.

- - - - -
098f80dd by Ben Gamari at 2023-09-27T12:20:18-04:00
IPE: Include unit id

- - - - -
6816ed1a by Ben Gamari at 2023-09-27T12:20:18-04:00
rts: Refactor GHC.Stack.CloneStack.decode

Don't allocate a Ptr constructor per frame.

- - - - -


23 changed files:

- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/StgToCmm/InfoTableProv.hs
- libraries/base/GHC/InfoProv.hs
- libraries/base/GHC/InfoProv/Types.hsc
- libraries/base/GHC/Stack/CloneStack.hs
- 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.stderr
- testsuite/tests/rts/ipe/ipeEventLog_fromMap.c
- testsuite/tests/rts/ipe/ipeEventLog_fromMap.stderr
- 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.hs
=====================================
@@ -34,7 +34,6 @@ module GHC.InfoProv
     ) where
 
 import GHC.Base
-import GHC.Ptr (nullPtr)
 import GHC.InfoProv.Types
 
 -- | Get information about where a value originated from.
@@ -49,14 +48,5 @@ import GHC.InfoProv.Types
 --
 -- @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
=====================================
@@ -12,12 +12,16 @@ module GHC.InfoProv.Types
     , 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)
 
@@ -26,6 +30,7 @@ data InfoProv = InfoProv {
   ipDesc :: String,
   ipTyDesc :: String,
   ipLabel :: String,
+  ipUnitId :: String,
   ipMod :: String,
   ipSrcFile :: String,
   ipSrcSpan :: String
@@ -36,18 +41,33 @@ ipLoc ipe = ipSrcFile ipe ++ ":" ++ ipSrcSpan ipe
 
 data InfoProvEnt
 
-getIPE :: a -> IO (Ptr InfoProvEnt)
-getIPE obj = IO $ \s ->
-   case whereFrom## obj s of
-     (## s', addr ##) -> (## s', Ptr addr ##)
+data StgInfoTable
+
+foreign import ccall "lookupIPE" c_lookupIPE :: Ptr StgInfoTable -> Ptr InfoProvEnt -> 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 (ipeProv 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, peekIpModule, peekIpSrcFile, peekIpSrcSpan, peekIpTyDesc :: Ptr InfoProv -> IO CString
+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
@@ -59,6 +79,7 @@ peekInfoProv infop = do
   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
@@ -67,6 +88,7 @@ peekInfoProv infop = do
       ipDesc = desc,
       ipTyDesc = tyDesc,
       ipLabel = label,
+      ipUnitId = unit_id,
       ipMod = mod,
       ipSrcFile = file,
       ipSrcSpan = span


=====================================
libraries/base/GHC/Stack/CloneStack.hs
=====================================
@@ -26,9 +26,10 @@ import Control.Concurrent.MVar
 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.Types (InfoProv (..), InfoProvEnt, ipLoc, ipeProv, peekInfoProv)
+import GHC.Ptr (Ptr(..))
+import GHC.Exts (Int (I#), RealWorld, StackSnapshot#, ThreadId#, ByteArray#, sizeofByteArray#, indexAddrArray#, State#, StablePtr#)
+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 +37,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, ByteArray# #)
 
 foreign import prim "stg_cloneMyStackzh" cloneMyStack# :: State# RealWorld -> (# State# RealWorld, StackSnapshot# #)
 
@@ -228,37 +229,31 @@ 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 #) ->
+      let n = I# (sizeofByteArray# arr) `div` 8 - 1
+       in unIO (go arr n) 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
-    stackEntryAt stack (I# i) = case indexArray# stack i of
-      (# se #) -> se
+    go :: ByteArray# -> 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 :: ByteArray# -> Int -> Ptr StgInfoTable
+    stackEntryAt stack (I# i) = Ptr (indexAddrArray# stack i)


=====================================
rts/CloneStack.c
=====================================
@@ -27,9 +27,8 @@
 
 static StgWord getStackFrameCount(StgStack* stack);
 static StgWord getStackChunkClosureCount(StgStack* stack);
-static void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack);
-static StgClosure* createPtrClosure(Capability* cap, InfoProvEnt* ipe);
-static StgMutArrPtrs* allocateMutableArray(StgWord size);
+static StgArrBytes* allocateByteArray(Capability *cap, StgWord bytes);
+static void copyPtrsToArray(StgArrBytes* arr, StgStack* stack);
 
 static StgStack* cloneStackChunk(Capability* capability, const StgStack* stack)
 {
@@ -115,12 +114,12 @@ void sendCloneStackMessage(StgTSO *tso STG_UNUSED, HsStablePtr mvar STG_UNUSED)
 // array is the count of stack frames.
 // Each InfoProvEnt* is looked up by lookupIPE(). If there's no IPE for a stack
 // frame it's represented by null.
-StgMutArrPtrs* decodeClonedStack(Capability *cap, StgStack* stack) {
+StgArrBytes* decodeClonedStack(Capability *cap, StgStack* stack) {
   StgWord closureCount = getStackFrameCount(stack);
 
-  StgMutArrPtrs* array = allocateMutableArray(closureCount);
+  StgArrBytes* array = allocateByteArray(cap, sizeof(StgInfoTable*) * closureCount);
 
-  copyPtrsToArray(cap, array, stack);
+  copyPtrsToArray(array, stack);
 
   return array;
 }
@@ -156,54 +155,33 @@ StgWord getStackChunkClosureCount(StgStack* stack) {
     return closureCount;
 }
 
-// Allocate and initialize memory for a MutableArray# (Haskell representation).
-StgMutArrPtrs* allocateMutableArray(StgWord closureCount) {
+// Allocate and initialize memory for a ByteArray# (Haskell representation).
+StgArrBytes* allocateByteArray(Capability *cap, StgWord bytes) {
   // Idea stolen from PrimOps.cmm:stg_newArrayzh()
-  StgWord size = closureCount + mutArrPtrsCardTableSize(closureCount);
-  StgWord words = sizeofW(StgMutArrPtrs) + size;
+  StgWord words = sizeofW(StgArrBytes) + bytes;
 
-  StgMutArrPtrs* array = (StgMutArrPtrs*) allocate(myTask()->cap, words);
-
-  SET_HDR(array, &stg_MUT_ARR_PTRS_DIRTY_info, CCS_SYSTEM);
-  array->ptrs  = closureCount;
-  array->size = size;
+  StgArrBytes* array = (StgArrBytes*) allocate(cap, words);
 
+  SET_HDR(array, &stg_ARR_WORDS_info, CCS_SYSTEM);
+  array->bytes  = bytes;
   return array;
 }
 
-
-void copyPtrsToArray(Capability *cap, StgMutArrPtrs* arr, StgStack* stack) {
+static void copyPtrsToArray(StgArrBytes* arr, StgStack* stack) {
   StgWord index = 0;
   StgStack *last_stack = stack;
+  const StgInfoTable **result = (const StgInfoTable **) arr->payload;
   while (true) {
     StgPtr sp = last_stack->sp;
     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);
-
+      const StgInfoTable* infoTable = ((StgClosure *)sp)->header.info;
+      result[index] = infoTable;
       index++;
     }
 
     // Ensure that we didn't overflow the result array
-    ASSERT(index-1 < arr->ptrs);
+    ASSERT(index-1 < arr->bytes / sizeof(StgInfoTable*));
 
     // check whether the stack ends in an underflow frame
     StgUnderflowFrame *frame = (StgUnderflowFrame *) (last_stack->stack
@@ -215,12 +193,3 @@ 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) {
-  StgClosure *p = (StgClosure *) allocate(cap, CONSTR_sizeW(0,1));
-  SET_HDR(p, &base_GHCziPtr_Ptr_con_info, CCS_SYSTEM);
-  p->payload[0] = (StgClosure*) ipe;
-  return TAG_CLOSURE(1, p);
-}


=====================================
rts/CloneStack.h
=====================================
@@ -15,7 +15,7 @@ StgStack* cloneStack(Capability* capability, const StgStack* stack);
 
 void sendCloneStackMessage(StgTSO *tso, HsStablePtr mvar);
 
-StgMutArrPtrs* decodeClonedStack(Capability *cap, StgStack* stack);
+StgArrBytes* decodeClonedStack(Capability *cap, StgStack* stack);
 
 #include "BeginPrivate.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;
+    CBool success;
     W_ info;
     info = GET_INFO(UNTAG(clos));
-    (ipe) = foreign "C" lookupIPE(info "ptr");
-    return (ipe);
+    (success) = foreign "C" lookupIPE(info, buf);
+    return (TO_W_(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


=====================================
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#
@@ -7927,7 +7927,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


=====================================
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, Int# #)
   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#
@@ -8182,7 +8182,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


=====================================
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#
@@ -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


=====================================
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.stderr
=====================================
@@ -1,20 +1,20 @@
-7f5278bc0740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, module module_000, srcloc src_file_000:src_span_000
-7f5278bc0740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, module module_001, srcloc src_file_001:src_span_001
-7f5278bc0740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, module module_002, srcloc src_file_002:src_span_002
-7f5278bc0740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, module module_003, srcloc src_file_003:src_span_003
-7f5278bc0740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, module module_004, srcloc src_file_004:src_span_004
-7f5278bc0740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, module module_005, srcloc src_file_005:src_span_005
-7f5278bc0740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, module module_006, srcloc src_file_006:src_span_006
-7f5278bc0740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, module module_007, srcloc src_file_007:src_span_007
-7f5278bc0740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, module module_008, srcloc src_file_008:src_span_008
-7f5278bc0740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, module module_009, srcloc src_file_009:src_span_009
-7f5278bc0740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, module module_000, srcloc src_file_000:src_span_000
-7f5278bc0740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, module module_001, srcloc src_file_001:src_span_001
-7f5278bc0740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, module module_002, srcloc src_file_002:src_span_002
-7f5278bc0740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, module module_003, srcloc src_file_003:src_span_003
-7f5278bc0740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, module module_004, srcloc src_file_004:src_span_004
-7f5278bc0740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, module module_005, srcloc src_file_005:src_span_005
-7f5278bc0740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, module module_006, srcloc src_file_006:src_span_006
-7f5278bc0740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, module module_007, srcloc src_file_007:src_span_007
-7f5278bc0740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, module module_008, srcloc src_file_008:src_span_008
-7f5278bc0740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, module module_009, srcloc src_file_009:src_span_009
+7ffff7a4d740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, unit unit_id_000, module module_000, srcloc src_file_000:src_span_000
+7ffff7a4d740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, unit unit_id_000, module module_000, srcloc src_file_001:src_span_001
+7ffff7a4d740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, unit unit_id_000, module module_000, srcloc src_file_002:src_span_002
+7ffff7a4d740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, unit unit_id_000, module module_000, srcloc src_file_003:src_span_003
+7ffff7a4d740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, unit unit_id_000, module module_000, srcloc src_file_004:src_span_004
+7ffff7a4d740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, unit unit_id_000, module module_000, srcloc src_file_005:src_span_005
+7ffff7a4d740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, unit unit_id_000, module module_000, srcloc src_file_006:src_span_006
+7ffff7a4d740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, unit unit_id_000, module module_000, srcloc src_file_007:src_span_007
+7ffff7a4d740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, unit unit_id_000, module module_000, srcloc src_file_008:src_span_008
+7ffff7a4d740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, unit unit_id_000, module module_000, srcloc src_file_009:src_span_009
+7ffff7a4d740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, unit unit_id_000, module module_000, srcloc src_file_000:src_span_000
+7ffff7a4d740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, unit unit_id_000, module module_000, srcloc src_file_001:src_span_001
+7ffff7a4d740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, unit unit_id_000, module module_000, srcloc src_file_002:src_span_002
+7ffff7a4d740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, unit unit_id_000, module module_000, srcloc src_file_003:src_span_003
+7ffff7a4d740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, unit unit_id_000, module module_000, srcloc src_file_004:src_span_004
+7ffff7a4d740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, unit unit_id_000, module module_000, srcloc src_file_005:src_span_005
+7ffff7a4d740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, unit unit_id_000, module module_000, srcloc src_file_006:src_span_006
+7ffff7a4d740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, unit unit_id_000, module module_000, srcloc src_file_007:src_span_007
+7ffff7a4d740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, unit unit_id_000, module module_000, srcloc src_file_008:src_span_008
+7ffff7a4d740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, unit unit_id_000, module module_000, srcloc src_file_009:src_span_009


=====================================
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/ipeEventLog_fromMap.stderr
=====================================
@@ -1,68 +1,20 @@
-7f86c4be8740: created capset 0 of type 2
-7f86c4be8740: created capset 1 of type 3
-7f86c4be8740: cap 0: initialised
-7f86c4be8740: assigned cap 0 to capset 0
-7f86c4be8740: assigned cap 0 to capset 1
-7f86c4be8740: cap 0: created thread 1[""]
-7f86c4be8740: cap 0: running thread 1[""] (ThreadRunGHC)
-7f86c4be8740: cap 0: thread 1[""] stopped (stack overflow, size 109)
-7f86c4be8740: cap 0: running thread 1[""] (ThreadRunGHC)
-7f86c4be8740: cap 0: created thread 2[""]
-7f86c4be8740: cap 0: thread 2 has label IOManager on cap 0
-7f86c4be8740: cap 0: thread 1[""] stopped (yielding)
-7f86b67fc640: cap 0: running thread 2["IOManager on cap 0"] (ThreadRunGHC)
-7f86b67fc640: cap 0: thread 2["IOManager on cap 0"] stopped (yielding)
-7f86c4be8740: cap 0: running thread 1[""] (ThreadRunGHC)
-7f86c4be8740: cap 0: created thread 3[""]
-7f86c4be8740: cap 0: thread 3 has label TimerManager
-7f86c4be8740: cap 0: thread 1[""] stopped (finished)
-7f86c4be8740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, module module_009, srcloc src_file_009:src_span_009
-7f86c4be8740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, module module_008, srcloc src_file_008:src_span_008
-7f86c4be8740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, module module_007, srcloc src_file_007:src_span_007
-7f86c4be8740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, module module_006, srcloc src_file_006:src_span_006
-7f86c4be8740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, module module_005, srcloc src_file_005:src_span_005
-7f86c4be8740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, module module_004, srcloc src_file_004:src_span_004
-7f86c4be8740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, module module_003, srcloc src_file_003:src_span_003
-7f86c4be8740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, module module_002, srcloc src_file_002:src_span_002
-7f86c4be8740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, module module_001, srcloc src_file_001:src_span_001
-7f86c4be8740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, module module_000, srcloc src_file_000:src_span_000
-7f86c4be8740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, module module_009, srcloc src_file_009:src_span_009
-7f86c4be8740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, module module_008, srcloc src_file_008:src_span_008
-7f86c4be8740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, module module_007, srcloc src_file_007:src_span_007
-7f86c4be8740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, module module_006, srcloc src_file_006:src_span_006
-7f86c4be8740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, module module_005, srcloc src_file_005:src_span_005
-7f86c4be8740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, module module_004, srcloc src_file_004:src_span_004
-7f86c4be8740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, module module_003, srcloc src_file_003:src_span_003
-7f86c4be8740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, module module_002, srcloc src_file_002:src_span_002
-7f86c4be8740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, module module_001, srcloc src_file_001:src_span_001
-7f86c4be8740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, module module_000, srcloc src_file_000:src_span_000
-7f86c4be8740: cap 0: created thread 4[""]
-7f86b67fc640: cap 0: running thread 2["IOManager on cap 0"] (ThreadRunGHC)
-7f86b67fc640: cap 0: thread 2["IOManager on cap 0"] stopped (suspended while making a foreign call)
-7f86b5ffb640: cap 0: running thread 3["TimerManager"] (ThreadRunGHC)
-7f86b5ffb640: cap 0: thread 3["TimerManager"] stopped (suspended while making a foreign call)
-7f86c4be8740: cap 0: running thread 4[""] (ThreadRunGHC)
-7f86c4be8740: cap 0: thread 4[""] stopped (yielding)
-7f86c4be8740: cap 0: running thread 4[""] (ThreadRunGHC)
-7f86c4be8740: cap 0: thread 4[""] stopped (finished)
-7f86b57fa640: cap 0: requesting sequential GC
-7f86b57fa640: cap 0: starting GC
-7f86b57fa640: cap 0: GC working
-7f86b57fa640: cap 0: GC idle
-7f86b57fa640: cap 0: GC done
-7f86b57fa640: cap 0: GC idle
-7f86b57fa640: cap 0: GC done
-7f86b57fa640: cap 0: GC idle
-7f86b57fa640: cap 0: GC done
-7f86b57fa640: cap 0: Memory Return (Current: 6) (Needed: 8) (Returned: 0)
-7f86b57fa640: cap 0: all caps stopped for GC
-7f86b57fa640: cap 0: finished GC
-7f86b5ffb640: cap 0: running thread 3["TimerManager"] (ThreadRunGHC)
-7f86b5ffb640: cap 0: thread 3["TimerManager"] stopped (finished)
-7f86b67fc640: cap 0: running thread 2["IOManager on cap 0"] (ThreadRunGHC)
-7f86b67fc640: cap 0: thread 2["IOManager on cap 0"] stopped (finished)
-7f86c4be8740: removed cap 0 from capset 0
-7f86c4be8740: removed cap 0 from capset 1
-7f86c4be8740: cap 0: shutting down
-7f86c4be8740: deleted capset 0
-7f86c4be8740: deleted capset 1
+7ffff7a4d740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, unit unit_id_000, module module_000, srcloc src_file_009:src_span_009
+7ffff7a4d740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, unit unit_id_000, module module_000, srcloc src_file_008:src_span_008
+7ffff7a4d740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, unit unit_id_000, module module_000, srcloc src_file_007:src_span_007
+7ffff7a4d740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, unit unit_id_000, module module_000, srcloc src_file_006:src_span_006
+7ffff7a4d740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, unit unit_id_000, module module_000, srcloc src_file_005:src_span_005
+7ffff7a4d740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, unit unit_id_000, module module_000, srcloc src_file_004:src_span_004
+7ffff7a4d740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, unit unit_id_000, module module_000, srcloc src_file_003:src_span_003
+7ffff7a4d740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, unit unit_id_000, module module_000, srcloc src_file_002:src_span_002
+7ffff7a4d740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, unit unit_id_000, module module_000, srcloc src_file_001:src_span_001
+7ffff7a4d740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, unit unit_id_000, module module_000, srcloc src_file_000:src_span_000
+7ffff7a4d740: IPE: table_name table_name_009, closure_desc closure_desc_009, ty_desc ty_desc_009, label label_009, unit unit_id_000, module module_000, srcloc src_file_009:src_span_009
+7ffff7a4d740: IPE: table_name table_name_008, closure_desc closure_desc_008, ty_desc ty_desc_008, label label_008, unit unit_id_000, module module_000, srcloc src_file_008:src_span_008
+7ffff7a4d740: IPE: table_name table_name_007, closure_desc closure_desc_007, ty_desc ty_desc_007, label label_007, unit unit_id_000, module module_000, srcloc src_file_007:src_span_007
+7ffff7a4d740: IPE: table_name table_name_006, closure_desc closure_desc_006, ty_desc ty_desc_006, label label_006, unit unit_id_000, module module_000, srcloc src_file_006:src_span_006
+7ffff7a4d740: IPE: table_name table_name_005, closure_desc closure_desc_005, ty_desc ty_desc_005, label label_005, unit unit_id_000, module module_000, srcloc src_file_005:src_span_005
+7ffff7a4d740: IPE: table_name table_name_004, closure_desc closure_desc_004, ty_desc ty_desc_004, label label_004, unit unit_id_000, module module_000, srcloc src_file_004:src_span_004
+7ffff7a4d740: IPE: table_name table_name_003, closure_desc closure_desc_003, ty_desc ty_desc_003, label label_003, unit unit_id_000, module module_000, srcloc src_file_003:src_span_003
+7ffff7a4d740: IPE: table_name table_name_002, closure_desc closure_desc_002, ty_desc ty_desc_002, label label_002, unit unit_id_000, module module_000, srcloc src_file_002:src_span_002
+7ffff7a4d740: IPE: table_name table_name_001, closure_desc closure_desc_001, ty_desc ty_desc_001, label label_001, unit unit_id_000, module module_000, srcloc src_file_001:src_span_001
+7ffff7a4d740: IPE: table_name table_name_000, closure_desc closure_desc_000, ty_desc ty_desc_000, label label_000, unit unit_id_000, module module_000, srcloc src_file_000:src_span_000


=====================================
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/2f0ac4c13157fdc6cbd18cfa00f4ba90d8c277ab...6816ed1a8963b0f49635159e7f0a6fba293e9bac

-- 
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/2f0ac4c13157fdc6cbd18cfa00f4ba90d8c277ab...6816ed1a8963b0f49635159e7f0a6fba293e9bac
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/20230927/ea0dfd50/attachment-0001.html>


More information about the ghc-commits mailing list