summaryrefslogtreecommitdiff
path: root/rts/IPE.c
blob: 63df8be7005d29b55d655028c3149a279e48c49b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 1998-2021
 *
 * Support for mapping info table pointers to source locations
 *
 * ---------------------------------------------------------------------------*/


#include "rts/PosixSource.h"
#include "Rts.h"

#include "Capability.h"
#include "Hash.h"
#include "IPE.h"
#include "Printer.h"
#include "Profiling.h"
#include "RtsUtils.h"

#include <fs_rts.h>
#include <string.h>

#if defined(TRACING)
#include "Trace.h"
#endif

/*
Note [The Info Table Provenance Entry (IPE) Map]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
IPEs are stored in a hash map from info table address (pointer) to IPE. This
ensures cheap lookup and traversal.

Unfortunately, inserting into the hash map is relatively expensive. To keep
startup times low, there's a temporary data structure that is optimized for
collecting IPE lists on registration.

It's a singly linked list of IPE list buffers (IpeBufferListNode). These are
emitted by the code generator, with generally one produced per module. Each
contains an array of IPE entries and a link field (which is used to link
buffers onto the pending list.

For reasons of space efficiency, IPE entries are represented slightly
differently in the object file than the InfoProvEnt which we ultimately expose
to the user. Specifically, the IPEs in IpeBufferListNode are represented by
IpeBufferEntrys, along with a corresponding string table. The string fields
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.

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.

After the content of a IpeBufferListNode has been inserted, it's freed.
*/

static Mutex ipeMapLock;
static HashTable *ipeMap = NULL;

// Accessed atomically
static IpeBufferListNode *ipeBufferList = NULL;

#if defined(THREADED_RTS)

void initIpe(void) { initMutex(&ipeMapLock); }

void exitIpe(void) { closeMutex(&ipeMapLock); }

#else

void initIpe(void) { }

void exitIpe(void) { }

#endif // THREADED_RTS

static InfoProvEnt ipeBufferEntryToIpe(const IpeBufferListNode *node, const IpeBufferEntry *ent)
{
    const char *strings = node->string_table;
    return (InfoProvEnt) {
            .info = ent->info,
            .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]
            }
    };
}

#if defined(TRACING)
static void traceIPEFromHashTable(void *data STG_UNUSED, StgWord key STG_UNUSED,
                                  const void *value) {
    InfoProvEnt *ipe = (InfoProvEnt *)value;
    traceIPE(ipe);
}

void dumpIPEToEventLog(void) {
    // Dump pending entries
    IpeBufferListNode *cursor = RELAXED_LOAD(&ipeBufferList);
    while (cursor != NULL) {
        for (uint32_t i = 0; i < cursor->count; i++) {
            const InfoProvEnt ent = ipeBufferEntryToIpe(cursor, &cursor->entries[i]);
            traceIPE(&ent);
        }
        cursor = cursor->next;
    }

    // Dump entries already in hashmap
    ACQUIRE_LOCK(&ipeMapLock);
    if (ipeMap != NULL) {
        mapHashTable(ipeMap, NULL, &traceIPEFromHashTable);
    }
    RELEASE_LOCK(&ipeMapLock);
}

#else

void dumpIPEToEventLog(void) { }

#endif

/* Registering IPEs

Adds the ent_list to the temporary buffer structure described in
Note [The Info Table Provenance Entry (IPE) Map].

Statically initialized IPE lists are registered at startup by a C constructor
function generated by the compiler (CodeOutput.hs) in a *.c file for each
module. Since this is called in a static initializer we cannot rely on
ipeMapLock; we instead use atomic CAS operations to add to the list.

A performance test for IPE registration and lookup can be found here:
https://gitlab.haskell.org/ghc/ghc/-/merge_requests/5724#note_370806
*/
void registerInfoProvList(IpeBufferListNode *node) {
    while (true) {
        IpeBufferListNode *old = RELAXED_LOAD(&ipeBufferList);
        node->next = old;
        if (cas_ptr((volatile void **) &ipeBufferList, old, node) == (void *) old) {
            return;
        }
    }
}

InfoProvEnt *lookupIPE(const StgInfoTable *info) {
    updateIpeMap();
    return lookupHashTable(ipeMap, (StgWord)info);
}

void updateIpeMap() {
    // Check if there's any work at all. If not so, we can circumvent locking,
    // which decreases performance.
    IpeBufferListNode *pending = xchg_ptr((void **) &ipeBufferList, NULL);
    if (ipeMap != NULL && pending == NULL) {
        return;
    }

    ACQUIRE_LOCK(&ipeMapLock);

    if (ipeMap == NULL) {
        ipeMap = allocHashTable();
    }

    while (pending != NULL) {
        IpeBufferListNode *currentNode = pending;
        InfoProvEnt *ip_ents = stgMallocBytes(sizeof(InfoProvEnt) * currentNode->count, "updateIpeMap");
        for (uint32_t i = 0; i < currentNode->count; i++) {
            const IpeBufferEntry *ent = &currentNode->entries[i];
            ip_ents[i] = ipeBufferEntryToIpe(currentNode, ent);
            insertHashTable(ipeMap, (StgWord) ent->info, &ip_ents[i]);
        }

        pending = currentNode->next;
    }

    RELEASE_LOCK(&ipeMapLock);
}