/* -----------------------------------------------------------------------------
 *
 * (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;

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

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

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],
                .srcloc = &strings[ent->srcloc]
            }
    };
}

#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(void) {
    // 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);
}
