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

#include "compiler.h"
#include "nasmlib.h"
#include "ilog2.h"
#include "saa.h"

/* Aggregate SAA components smaller than this */
#define SAA_BLKSHIFT	16
#define SAA_BLKLEN	((size_t)1 << SAA_BLKSHIFT)

struct SAA *saa_init(size_t elem_len)
{
    struct SAA *s;
    char *data;

    s = nasm_zalloc(sizeof(struct SAA));

    if (elem_len >= SAA_BLKLEN)
        s->blk_len = elem_len;
    else
        s->blk_len = SAA_BLKLEN - (SAA_BLKLEN % elem_len);

    s->elem_len = elem_len;
    s->length = s->blk_len;
    data = nasm_malloc(s->blk_len);
    s->nblkptrs = s->nblks = 1;
    s->blk_ptrs = nasm_malloc(sizeof(char *));
    s->blk_ptrs[0] = data;
    s->wblk = s->rblk = &s->blk_ptrs[0];

    return s;
}

void saa_free(struct SAA *s)
{
    char **p;
    size_t n;

    for (p = s->blk_ptrs, n = s->nblks; n; p++, n--)
        nasm_free(*p);

    nasm_free(s->blk_ptrs);
    nasm_free(s);
}

/* Add one allocation block to an SAA */
static void saa_extend(struct SAA *s)
{
    size_t blkn = s->nblks++;

    if (blkn >= s->nblkptrs) {
        size_t rindex = s->rblk - s->blk_ptrs;
        size_t windex = s->wblk - s->blk_ptrs;

        s->nblkptrs <<= 1;
        s->blk_ptrs =
            nasm_realloc(s->blk_ptrs, s->nblkptrs * sizeof(char *));

        s->rblk = s->blk_ptrs + rindex;
        s->wblk = s->blk_ptrs + windex;
    }

    s->blk_ptrs[blkn] = nasm_malloc(s->blk_len);
    s->length += s->blk_len;
}

void *saa_wstruct(struct SAA *s)
{
    void *p;

    nasm_assert((s->wpos % s->elem_len) == 0);

    if (s->wpos + s->elem_len > s->blk_len) {
        nasm_assert(s->wpos == s->blk_len);
        if (s->wptr + s->elem_len > s->length)
            saa_extend(s);
        s->wblk++;
        s->wpos = 0;
    }

    p = *s->wblk + s->wpos;
    s->wpos += s->elem_len;
    s->wptr += s->elem_len;

    if (s->wptr > s->datalen)
        s->datalen = s->wptr;

    return p;
}

void saa_wbytes(struct SAA *s, const void *data, size_t len)
{
    const char *d = data;

    while (len) {
        size_t l = s->blk_len - s->wpos;
        if (l > len)
            l = len;
        if (l) {
            if (d) {
                memcpy(*s->wblk + s->wpos, d, l);
                d += l;
            } else
                memset(*s->wblk + s->wpos, 0, l);
            s->wpos += l;
            s->wptr += l;
            len -= l;

            if (s->datalen < s->wptr)
                s->datalen = s->wptr;
        }
        if (len) {
            if (s->wptr >= s->length)
                saa_extend(s);
            s->wblk++;
            s->wpos = 0;
        }
    }
}

/*
 * Writes a string, *including* the final null, to the specified SAA,
 * and return the number of bytes written.
 */
size_t saa_wcstring(struct SAA *s, const char *str)
{
    size_t bytes = strlen(str) + 1;

    saa_wbytes(s, str, bytes);

    return bytes;
}

void saa_rewind(struct SAA *s)
{
    s->rblk = s->blk_ptrs;
    s->rpos = s->rptr = 0;
}

void *saa_rstruct(struct SAA *s)
{
    void *p;

    if (s->rptr + s->elem_len > s->datalen)
        return NULL;

    nasm_assert((s->rpos % s->elem_len) == 0);

    if (s->rpos + s->elem_len > s->blk_len) {
        s->rblk++;
        s->rpos = 0;
    }

    p = *s->rblk + s->rpos;
    s->rpos += s->elem_len;
    s->rptr += s->elem_len;

    return p;
}

const void *saa_rbytes(struct SAA *s, size_t * lenp)
{
    const void *p;
    size_t len;

    if (s->rptr >= s->datalen) {
        *lenp = 0;
        return NULL;
    }

    if (s->rpos >= s->blk_len) {
        s->rblk++;
        s->rpos = 0;
    }

    len = *lenp;
    if (len > s->datalen - s->rptr)
        len = s->datalen - s->rptr;
    if (len > s->blk_len - s->rpos)
        len = s->blk_len - s->rpos;

    *lenp = len;
    p = *s->rblk + s->rpos;

    s->rpos += len;
    s->rptr += len;

    return p;
}

void saa_rnbytes(struct SAA *s, void *data, size_t len)
{
    char *d = data;

    nasm_assert(s->rptr + len <= s->datalen);

    while (len) {
        size_t l;
        const void *p;

        l = len;
        p = saa_rbytes(s, &l);

        memcpy(d, p, l);
        d += l;
        len -= l;
    }
}

/* Same as saa_rnbytes, except position the counter first */
void saa_fread(struct SAA *s, size_t posn, void *data, size_t len)
{
    size_t ix;

    nasm_assert(posn + len <= s->datalen);

    if (likely(s->blk_len == SAA_BLKLEN)) {
        ix = posn >> SAA_BLKSHIFT;
        s->rpos = posn & (SAA_BLKLEN - 1);
    } else {
        ix = posn / s->blk_len;
        s->rpos = posn % s->blk_len;
    }
    s->rptr = posn;
    s->rblk = &s->blk_ptrs[ix];

    saa_rnbytes(s, data, len);
}

/* Same as saa_wbytes, except position the counter first */
void saa_fwrite(struct SAA *s, size_t posn, const void *data, size_t len)
{
    size_t ix;
    size_t padding = 0;

    if (posn > s->datalen) {
        padding = posn - s->datalen;
        posn = s->datalen;
    }

    if (likely(s->blk_len == SAA_BLKLEN)) {
        ix = posn >> SAA_BLKSHIFT;
        s->wpos = posn & (SAA_BLKLEN - 1);
    } else {
        ix = posn / s->blk_len;
        s->wpos = posn % s->blk_len;
    }
    s->wptr = posn;
    s->wblk = &s->blk_ptrs[ix];

    if (!s->wpos) {
        s->wpos = s->blk_len;
        s->wblk--;
    }

    if (padding)
        saa_wbytes(s, NULL, padding);

    saa_wbytes(s, data, len);
}

void saa_fpwrite(struct SAA *s, FILE * fp)
{
    const char *data;
    size_t len;

    saa_rewind(s);
    while (len = s->datalen, (data = saa_rbytes(s, &len)) != NULL)
        nasm_write(data, len, fp);
}

void saa_write8(struct SAA *s, uint8_t v)
{
    saa_wbytes(s, &v, 1);
}

void saa_write16(struct SAA *s, uint16_t v)
{
    v = htole16(v);
    saa_wbytes(s, &v, 2);
}

void saa_write32(struct SAA *s, uint32_t v)
{
    v = htole32(v);
    saa_wbytes(s, &v, 4);
}

void saa_write64(struct SAA *s, uint64_t v)
{
    v = htole64(v);
    saa_wbytes(s, &v, 8);
}

void saa_writeaddr(struct SAA *s, uint64_t v, size_t len)
{
    v = htole64(v);
    saa_wbytes(s, &v, len);
}

/*
 * Write an LEB128 value to an SAA. Each byte contains 7 bits of
 * payload in littleendian order, with the topmost bit indicating
 * continuation.
 *
 * For the signed format, the sign bit needs to be included, even if
 * it zero, so may generate an encoding that would be invalid for
 * the unsigned format, e.g 127:
 *
 * unsigned: 7F
 * signed:   FF 00
 *
 * Thus, the >> 6 instead of >> 7 in the exit test for the signed
 * version.
 */
void saa_wleb128u(struct SAA *s, uint64_t value)
{
    uint8_t buf[sizeof(value)*2]; /* Very conservative allocation :) */
    uint8_t *p = buf;
    uint8_t byte;

    while (1) {
        byte = value & 127;
        value >>= 7;
        if (!value) {
            *p++ = byte;
            break;
        }
        byte |= 0x80;
        *p++ = byte;
    }
    saa_wbytes(s, buf, p-buf);
}

/* write a signed LEB128 value to SAA */
void saa_wleb128s(struct SAA *s, int64_t value)
{
    uint8_t buf[sizeof(value)*2]; /* Very conservative allocation :) */
    uint8_t *p = buf;
    uint8_t byte;
    int64_t sign = value >> 63;

    while (1) {
        byte = value & 127;
        if ((value >> 6) == sign) {
            *p++ = byte;
            break;
        }
        value >>= 7;
        byte |= 0x80;
        *p++ = byte;
    }
    saa_wbytes(s, buf, p-buf);
}
