root/scripts/gendwarfksyms/die.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2024 Google LLC
 */

#include <string.h>
#include "gendwarfksyms.h"

#define DIE_HASH_BITS 16

/* {die->addr, state} -> struct die * */
static HASHTABLE_DEFINE(die_map, 1 << DIE_HASH_BITS);

static unsigned int map_hits;
static unsigned int map_misses;

static inline unsigned int die_hash(uintptr_t addr, enum die_state state)
{
        return hash_32(addr_hash(addr) ^ (unsigned int)state);
}

static void init_die(struct die *cd)
{
        cd->state = DIE_INCOMPLETE;
        cd->mapped = false;
        cd->fqn = NULL;
        cd->tag = -1;
        cd->addr = 0;
        INIT_LIST_HEAD(&cd->fragments);
}

static struct die *create_die(Dwarf_Die *die, enum die_state state)
{
        struct die *cd;

        cd = xmalloc(sizeof(*cd));
        init_die(cd);
        cd->addr = (uintptr_t)die->addr;

        hash_add(die_map, &cd->hash, die_hash(cd->addr, state));
        return cd;
}

int __die_map_get(uintptr_t addr, enum die_state state, struct die **res)
{
        struct die *cd;

        hash_for_each_possible(die_map, cd, hash, die_hash(addr, state)) {
                if (cd->addr == addr && cd->state == state) {
                        *res = cd;
                        return 0;
                }
        }

        return -1;
}

struct die *die_map_get(Dwarf_Die *die, enum die_state state)
{
        struct die *cd;

        if (__die_map_get((uintptr_t)die->addr, state, &cd) == 0) {
                map_hits++;
                return cd;
        }

        map_misses++;
        return create_die(die, state);
}

static void reset_die(struct die *cd)
{
        struct die_fragment *tmp;
        struct die_fragment *df;

        list_for_each_entry_safe(df, tmp, &cd->fragments, list) {
                if (df->type == FRAGMENT_STRING)
                        free(df->data.str);
                free(df);
        }

        if (cd->fqn && *cd->fqn)
                free(cd->fqn);
        init_die(cd);
}

void die_map_for_each(die_map_callback_t func, void *arg)
{
        struct hlist_node *tmp;
        struct die *cd;

        hash_for_each_safe(die_map, cd, tmp, hash) {
                func(cd, arg);
        }
}

void die_map_free(void)
{
        struct hlist_node *tmp;
        unsigned int stats[DIE_LAST + 1];
        struct die *cd;
        int i;

        memset(stats, 0, sizeof(stats));

        hash_for_each_safe(die_map, cd, tmp, hash) {
                stats[cd->state]++;
                reset_die(cd);
                free(cd);
        }
        hash_init(die_map);

        if (map_hits + map_misses > 0)
                debug("hits %u, misses %u (hit rate %.02f%%)", map_hits,
                      map_misses,
                      (100.0f * map_hits) / (map_hits + map_misses));

        for (i = 0; i <= DIE_LAST; i++)
                debug("%s: %u entries", die_state_name(i), stats[i]);
}

static struct die_fragment *append_item(struct die *cd)
{
        struct die_fragment *df;

        df = xmalloc(sizeof(*df));
        df->type = FRAGMENT_EMPTY;
        list_add_tail(&df->list, &cd->fragments);
        return df;
}

void die_map_add_string(struct die *cd, const char *str)
{
        struct die_fragment *df;

        if (!cd)
                return;

        df = append_item(cd);
        df->data.str = xstrdup(str);
        df->type = FRAGMENT_STRING;
}

void die_map_add_linebreak(struct die *cd, int linebreak)
{
        struct die_fragment *df;

        if (!cd)
                return;

        df = append_item(cd);
        df->data.linebreak = linebreak;
        df->type = FRAGMENT_LINEBREAK;
}

void die_map_add_die(struct die *cd, struct die *child)
{
        struct die_fragment *df;

        if (!cd)
                return;

        df = append_item(cd);
        df->data.addr = child->addr;
        df->type = FRAGMENT_DIE;
}