/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright 1996-2020 The NASM Authors - All Rights Reserved */

/*
 * outlib.c
 *
 * Common routines for the output backends.
 */

#include "outlib.h"
#include "raa.h"

uint64_t realsize(enum out_type type, uint64_t size)
{
    switch (type) {
    case OUT_REL1ADR:
	return 1;
    case OUT_REL2ADR:
	return 2;
    case OUT_REL4ADR:
	return 4;
    case OUT_REL8ADR:
	return 8;
    default:
	return size;
    }
}

/* Common section/symbol handling */

struct ol_sect *_ol_sect_list;
uint64_t _ol_nsects;             /* True sections, not external symbols */
static struct ol_sect **ol_sect_tail = &_ol_sect_list;
static struct hash_table ol_secthash;
static struct RAA *ol_sect_index_tbl;

struct ol_sym *_ol_sym_list;
uint64_t _ol_nsyms;
static struct ol_sym **ol_sym_tail = &_ol_sym_list;
static struct hash_table ol_symhash;

void ol_init(void)
{
}

static void ol_free_symbols(void)
{
    struct ol_sym *s, *stmp;

    hash_free(&ol_symhash);

    list_for_each_safe(s, stmp, _ol_sym_list) {
        nasm_free((char *)s->name);
        nasm_free(s);
    }

    _ol_nsyms = 0;
    _ol_sym_list = NULL;
    ol_sym_tail = &_ol_sym_list;
}

static void ol_free_sections(void)
{
    struct ol_sect *s, *stmp;

    hash_free(&ol_secthash);
    raa_free(ol_sect_index_tbl);
    ol_sect_index_tbl = NULL;

    list_for_each_safe(s, stmp, _ol_sect_list) {
        saa_free(s->data);
        saa_free(s->reloc);
        nasm_free((char *)s->name);
        nasm_free(s);
    }

    _ol_nsects = 0;
    _ol_sect_list = NULL;
    ol_sect_tail = &_ol_sect_list;
}

void ol_cleanup(void)
{
    ol_free_symbols();
    ol_free_sections();
}

/*
 * Allocate a section index and add a section, subsection, or external
 * symbol to the section-by-index table. If the index provided is zero,
 * allocate a new index via seg_alloc().
 */
static uint32_t ol_seg_alloc(void *s, uint32_t ix)
{
    if (!ix)
        ix = seg_alloc();
    ol_sect_index_tbl = raa_write_ptr(ol_sect_index_tbl, ix >> 1, s);
    return ix;
}

/*
 * Find a section or create a new section structure if it does not exist
 * and allocate it an index value via seg_alloc().
 */
struct ol_sect *_ol_get_sect(const char *name, size_t ssize, size_t rsize)
{
    struct ol_sect *s, **sp;
    struct hash_insert hi;

    sp = (struct ol_sect **)hash_find(&ol_secthash, name, &hi);
    if (sp)
        return *sp;

    s             = nasm_zalloc(ssize);
    s->syml.tail  = &s->syml.head;
    s->name       = nasm_strdup(name);
    s->data       = saa_init(1);
    s->reloc      = saa_init(rsize);
    *ol_sect_tail = s;
    ol_sect_tail  = &s->next;
    _ol_nsects++;
    s->index     = s->subindex = ol_seg_alloc(s, 0);

    hash_add(&hi, s->name, s);
    return s;
}

/* Find a section by name without creating one */
struct ol_sect *_ol_sect_by_name(const char *name)
{
    struct ol_sect **sp;

    sp = (struct ol_sect **)hash_find(&ol_secthash, name, NULL);
    return sp ? *sp : NULL;
}

/* Find a section or external symbol by index; NULL if not valid */
struct ol_sect *_ol_sect_by_index(int32_t index)
{
    uint32_t ix = index;

    if (unlikely(ix >= SEG_ABS))
        return NULL;

    return raa_read_ptr(ol_sect_index_tbl, ix >> 1);
}

/*
 * Start a new subsection for the given section. At the moment, once a
 * subsection has been created, it is not possible to revert to an
 * earlier subsection. ol_sect_by_index() will return the main section
 * structure. Returns the new section index.  This is used to prevent
 * the front end from optimizing across subsection boundaries.
 */
int32_t _ol_new_subsection(struct ol_sect *sect)
{
    if (unlikely(!sect))
        return NO_SEG;

    return sect->subindex = ol_seg_alloc(sect, 0);
}

/*
 * Insert a symbol into a list; need to use upcasting using container_of()
 * to walk the list later.
 */
static void ol_add_sym_to(struct ol_symlist *syml, struct ol_symhead *head,
                          uint64_t offset)
{
    syml->tree.key = offset;
    head->tree     = rb_insert(head->tree, &syml->tree);
    *head->tail    = syml;
    head->tail     = &syml->next;
    head->n++;
}

/*
 * Create a location structure from seg:offs
 */
void ol_mkloc(struct ol_loc *loc, int64_t offs, int32_t seg)
{
    nasm_zero(*loc);
    loc->offs = offs;

    if (unlikely((uint32_t)seg >= SEG_ABS)) {
        if (likely(seg == NO_SEG)) {
            loc->seg.t     = OS_NOSEG;
        } else {
            loc->seg.t     = OS_ABS;
            loc->seg.index = seg - SEG_ABS;
        }
    } else {
        loc->seg.index  = seg & ~1;
        loc->seg.t      = OS_SECT | (seg & 1);
        loc->seg.s.sect = _ol_sect_by_index(loc->seg.index);
    }
}

/*
 * Create a new symbol. If this symbol is OS_OFFS, add it to the relevant
 * section, too. If the symbol already exists, return NULL; this is
 * different from ol_get_section() as a single section may be invoked
 * many times. On the contrary, the front end will prevent a single symbol
 * from being defined more than once.
 *
 * If flags has OF_GLOBAL set, add it to the global symbol hash for
 * the containing section if applicable.
 *
 * If flags has OF_IMPSEC set, allocate a segment index for it via
 * seg_alloc() unless v->index is already set, and add it to the
 * section by index list.
 */
struct ol_sym *_ol_new_sym(const char *name, const struct ol_loc *v,
                           uint32_t flags, size_t size)
{
    struct hash_insert hi;
    struct ol_sym *sym;

    if (hash_find(&ol_symhash, name, &hi))
        return NULL;            /* Symbol already exists */

    flags     |= OF_SYMBOL;

    sym        = nasm_zalloc(size);
    sym->name  = nasm_strdup(name);
    sym->v     = *v;

    if (sym->v.seg.t & OS_SECT) {
        struct ol_sect *sect = sym->v.seg.s.sect;

        if (!sect || (sect->flags & OF_SYMBOL))
            /* Must be an external or common reference */
            flags |= OF_IMPSEC;

        if (flags & OF_IMPSEC) {
            /* Metasection */
            if (!sym->v.seg.s.sym) {
                sym->v.seg.s.sym = sym;
                sym->v.seg.index = ol_seg_alloc(sym, sym->v.seg.index);
            }
        } else if (sym->v.seg.t == OS_OFFS) {
            struct ol_sect * const sect = sym->v.seg.s.sect;
            const uint64_t offs = sym->v.offs;

            ol_add_sym_to(&sym->syml, &sect->syml, offs);
            if (flags & OF_GLOBAL)
                ol_add_sym_to(&sym->symg, &sect->symg, offs);
        }
    }
    sym->flags = flags;

    *ol_sym_tail = sym;
    ol_sym_tail  = &sym->next;
    _ol_nsyms++;

    hash_add(&hi, sym->name, sym);
    return sym;
}

/* Find a symbol in the global namespace */
struct ol_sym *_ol_sym_by_name(const char *name)
{
    struct ol_sym **symp;

    symp = (struct ol_sym **)hash_find(&ol_symhash, name, NULL);
    return symp ? *symp : NULL;
}

/*
 * Find a symbol by address in a specific section. If no symbol is defined
 * at that exact address, return the immediately previously defined one.
 * If global is set, then only return global symbols.
 */
struct ol_sym *_ol_sym_by_address(struct ol_sect *sect, int64_t addr,
                                  bool global)
{
    struct ol_symhead *head;
    size_t t_offs;
    struct rbtree *t;

    if (global) {
        head = &sect->symg;
        t_offs = offsetof(struct ol_sym, symg.tree);
    } else {
        head = &sect->syml;
        t_offs = offsetof(struct ol_sym, syml.tree);
    }

    t = rb_search(head->tree, addr);
    if (!t)
        return NULL;

    return (struct ol_sym *)((char *)t - t_offs);
}
