/*
Copyright (c) 2020, David Anderson
All rights reserved.

This software file is hereby placed in the public domain.
For use by anyone for any purpose.
*/

/* This uses this condensed table to make
   a simple fast-access C table.
   Reads dwarf.h to be sure the fast-access table
   has all the named DW_OP in dwarf.h present.
   Build and run with
   make rebuild
*/

#include <config.h>

#include <stdio.h>  /* FILE fclose() fopen() fprintf() printf() */
#include <stdlib.h> /* exit() getenv() */
#include <string.h> /* strcmp() strcpy() strncmp() strlen() */

#include "dwarf.h"
#include "libdwarf.h"
#include "libdwarf_private.h"
#include "dd_opscounttab.h"

#define MAXDEFINELINE 1000
static char *input_name = 0;
static char pathbuf[BUFSIZ];
static char buffer[BUFSIZ];

struct ops_table_s {
    unsigned char ot_first;
    unsigned char ot_last;
    signed   char ot_opcount;
    signed   char ot_stackchange;
};

/*  the ops are 8 bits max */
#define OPS_USED_SIZE    256
#define OPS_USED_DUPS    20
static int ops_used_table[OPS_USED_SIZE];
struct dups_tab_s {
    int dt_val;
    char dt_name1[100];
} dups[OPS_USED_DUPS];
int dups_used = 0;

static struct ops_table_s optabsource[]= {
{DW_OP_addr  ,         0               , 1, 1 },
{DW_OP_deref  ,        0               , 0, 0 },
{DW_OP_const1u,        DW_OP_consts    , 1, 1},
{DW_OP_dup,            DW_OP_over      , 0, 1},
{DW_OP_pick,           0               , 1, 1},
{DW_OP_swap,           0               , 0, 0},
{DW_OP_rot,            0               , 0, 0},
{DW_OP_xderef,         0               , 0, -1},
{DW_OP_abs,            0               , 0, 0},
{DW_OP_and,            DW_OP_mul       , 0, -1},
{DW_OP_neg,            DW_OP_not       , 0, 0},
{DW_OP_or,             DW_OP_plus      , 0, -1},
{DW_OP_plus_uconst,    0               , 1, 0},

{DW_OP_shl,            DW_OP_xor       , 0, -1},
{DW_OP_bra ,           0               , 1, -1},
{DW_OP_eq,             DW_OP_ne        , 0, -1},
{DW_OP_skip,           0               , 1, 0},
{DW_OP_lit0  ,         DW_OP_lit31     , 0, 1},
{DW_OP_reg0  ,         DW_OP_reg31     , 0, 0},
{DW_OP_breg0 ,         DW_OP_breg31    , 1, 1},
{DW_OP_regx  ,         DW_OP_fbreg     , 1, 0},
{DW_OP_bregx,          0               , 2, 1},
{DW_OP_piece,          0               , 1, 0},
{DW_OP_deref_size,     0               , 1, 0},
{DW_OP_xderef_size,    0               , 1, -1},
{DW_OP_nop,            0               , 0, 0},
{DW_OP_push_object_address ,0          , 0, 1},
/*  The called expr may change the stack,
    but not the call itself*/
{DW_OP_call2,          DW_OP_call_ref          , 1, 0},

{DW_OP_form_tls_address,0              , 0, 0},
{DW_OP_call_frame_cfa  ,0              , 0, 1},
{DW_OP_bit_piece,       0              , 2, 0},
{DW_OP_implicit_value,  0              , 2, 0},
{DW_OP_stack_value,     0              , 0, 0},
{DW_OP_implicit_pointer,0              , 2, 0},
{DW_OP_addrx,          DW_OP_constx    , 1, 1},
{DW_OP_entry_value,                   0, 2, 1},
{DW_OP_const_type,                    0, 3, 1},
{DW_OP_regval_type, DW_OP_deref_type   , 2, 1},
{DW_OP_xderef_type,                   0, 0, -1},
{DW_OP_convert /*0xa8*/,              0, 1, 0},
{DW_OP_reinterpret /* 0xa9*/,         0, 1, 0},
{DW_OP_GNU_push_tls_address /*0xe0*/, 0, 0, 1},
{DW_OP_HP_unknown /* 0xe0*/,          0, 0, 0},
{DW_OP_LLVM_form_aspace_address /* 0xe1*/,  0, 0, 1},
{DW_OP_HP_is_value /* 0xe1*/,         0, 1, 1},

{DW_OP_LLVM_push_lane /* 0xe2*/,      0, 0, 1},
{DW_OP_HP_fltconst4 /* 0xe2*/ ,       0, 1, 1},

{DW_OP_LLVM_offset /* 0xe3*/ ,        0, 0, 0},
{DW_OP_HP_fltconst8 /* 0xe3*/ ,       0, 1, 0},

{DW_OP_LLVM_offset_uconst /* 0xe4*/,  0, 1, 0},
{DW_OP_HP_mod_range /* 0xe4*/,        0, 2, 0},

{DW_OP_LLVM_bit_offset /* 0xe5*/,      0, 0, 0},
{DW_OP_HP_unmod_range /* 0xe5*/,      0, 2, 0},

{DW_OP_LLVM_call_frame_entry_reg /* 0xe6*/, 0, 1, 0},
{DW_OP_HP_tls /* 0xe6*/,              0, 0, 0},

{DW_OP_LLVM_undefined /* 0xe7*/,      0, 0, 0},
{DW_OP_LLVM_aspace_bregx /* 0xe8*/,   0, 2, 0},
{DW_OP_INTEL_bit_piece /* 0xe8*/,     0, 2, 0},
{DW_OP_LLVM_aspace_implicit_pointer /* 0xe9*/,   0, 2, 0},
{DW_OP_LLVM_piece_end /* 0xea*/,      0, 0, 0},
{DW_OP_LLVM_extend /* 0xeb*/,         0, 2, 0},

{DW_OP_LLVM_select_bit_piece /* 0xec*/, 0, 2, 0},
{DW_OP_WASM_location /* 0xed*/,       0, 1, 1},
{DW_OP_WASM_location_int /* 0xee*/,   0, 1, 1},

{DW_OP_GNU_uninit /* 0xf0*/,          0, 0, 0},/*unknown opcount*/
{DW_OP_APPLE_uninit /* 0xf0*/,        0, 1, 0},
{DW_OP_GNU_encoded_addr /*0xf1*/,     0, 1, 1},/*1 op is correct*/
{DW_OP_GNU_implicit_pointer /*0xf2*/, 0, 1, 1},/*1 op is correct*/
{DW_OP_GNU_entry_value /*0xf3*/,      0, 2, 1},/*2 op is correct*/
{DW_OP_GNU_const_type /*0xf4 */,      0, 3,1},/*3 is correct*/
{DW_OP_GNU_regval_type /* 0xf5*/,     0, 2,1},/*2 is correct*/
{DW_OP_GNU_deref_type /*0xf6*/,       0, 2,1},/*2 is correct*/
{DW_OP_GNU_convert /*0xf7*/,          0, 1,0},/*1 is correct*/
{DW_OP_PGI_omp_thread_num /*0xf8*/,   0, 0,1},/*just pushes*/
{DW_OP_GNU_reinterpret/* 0xf9*/,      0, 1,0},/*1 is correct*/
{DW_OP_GNU_parameter_ref/* 0xfa*/,    0, 1,1},/*1 is correct*/
{DW_OP_GNU_addr_index/* 0xfb*/,       0, 1,1},/*1 is correct.Fission*/
{DW_OP_GNU_const_index/* 0xfc*/,      0, 1,1},/*1 is correct.Fission*/
{DW_OP_GNU_variable_value/* 0xfd*/,   0, 1,1},/* GNU 2017*/
{0,0,0,0}
};

static void
safe_strcpy(char *targ,char *src,unsigned targlen, unsigned srclen)
{
    if (srclen > targlen) {
        printf("Target name does not fit in buffer.\n"
            "In buildopstabcount.c increase buffer size "
            " from %u \n",(unsigned int)sizeof(buffer));
        exit(EXIT_FAILURE);
    }
    strcpy(targ,src);
}

/*  Issue error message and exit there is a mismatch,
    this should never fail.  */
static void
validate_name(char *name,unsigned long v,unsigned int linenum)
{
    int res = 0;
    const char *s = 0;
    int count = 0;
    if (v >= OPS_USED_SIZE) {
        printf("FAIL! Opcode 0x%lx too large. Impossible\n", v);
        exit(EXIT_FAILURE);

    }
    ops_used_table[v]++;
    count = ops_used_table[v];
    res = dwarf_get_OP_name(v,&s);
    if (res != DW_DLV_OK) {
        printf("Fail dwarf.h line %u value %lu as no OP name! %s\n",
            linenum,v,name);
        exit(EXIT_FAILURE);
    }
    if ( count > 1) {
        fprintf(stderr,"Op 0x%lx used %d times. %s and now %s\n",
            v,count,s, name);
        if (dups_used >= OPS_USED_DUPS) {
            printf("Too many dups, increase table size\n");
            exit(EXIT_FAILURE);
        }
        dups[dups_used].dt_val = v;
        safe_strcpy(dups[dups_used].dt_name1,name,
            sizeof(dups[dups_used].dt_name1),strlen(name));
        dups_used++;
        return;
    }
    if (strcmp(name,s)) {
        printf("Fail dwarf.h line %u value %lu as  OP "
            "name mismatch! %s\n",
            linenum,v,name);
        exit(EXIT_FAILURE);
    }
}

/*  This is N*M overall but the numbers are small, so it's
    unimportant. */
static void
validate_op_listed(char *curdefname,unsigned long v,
    unsigned int linenum)
{
    struct ops_table_s * ops = optabsource ;

    for ( ; ; ++ops) {
        unsigned int j = 0;
        if (!ops->ot_first &&
            !ops->ot_last &&
            !ops->ot_opcount) {
            break;
        }
        if (v == ops->ot_first) {
            validate_name(curdefname,v,linenum);
            return;
        }
        if (ops->ot_last && v == ops->ot_last) {
            validate_name(curdefname,v,linenum);
            return;
        }
        for ( j = ops->ot_first; j < ops->ot_last; ++j) {
            if (v == j) {
                validate_name(curdefname,v,linenum);
                return;
            }
        }
    }
    printf("Failed to find %s val 0x%lu at dwarf.h "
        "line %u in optabsource\n",
        curdefname,v, linenum);
    exit(EXIT_FAILURE);
}

static void
check_if_optabsource_complete(char *path)
{
    FILE *fin = 0;
    unsigned int linenum = 0;
    int loop_done = FALSE;
    const char *oldop = "#define DW_OP_";
    int   oldoplen = strlen(oldop);

    fin = fopen(path,"r");
    if (!fin) {
        printf("Unable to open dwarf.h to read %s\n",path);
        exit(EXIT_FAILURE);
    }
    for ( ;!loop_done;++linenum) {
        char *line = 0;
        char * pastname  = 0;
        unsigned int linelen = 0;
        char *curdefname = 0;
        unsigned int curdefnamelen = 0;
        char *endptr     = 0;
        char *numstart   = 0;
        unsigned long v  = 0;

        line = fgets(buffer,MAXDEFINELINE,fin);
        if (!line) {
            break;
        }
        linelen = strlen(line);
        line[linelen-1] = 0;
        --linelen;
        if (linelen >= (unsigned)(MAXDEFINELINE-1)) {
            printf("define line %u is too long!\n",linenum);
            exit(EXIT_FAILURE);
        }
        if (strncmp(line,oldop,oldoplen)) {
            /* Not ours. */
            continue;
        }
        /* ASSERT: line ends with NUL byte. */
        curdefname = line+8;
        for ( ; ; curdefnamelen++) {
            pastname = curdefname +curdefnamelen;
            if (!*pastname) {
                /* At end of line. Missing value. */
                printf("define line %u of %s: has no number value!\n",
                    linenum,path);
                exit(EXIT_FAILURE);
            }
            if (*pastname == ' ') {
                /* Ok. Now look for value. */
                numstart = pastname + 1;
                break;
            }
        }
        *pastname = 0;
        if (!strcmp(curdefname,"DW_OP_lo_user")) {
            /* This is special, we ignore it. */
            continue;
        }
        if (!strcmp(curdefname,"DW_OP_hi_user")) {
            /* This is special, we ignore it. */
            continue;
        }
        /*  Skip spaces. */
        for ( ; *numstart == ' '; ++numstart) { }
        endptr = 0;
        v = strtoul(numstart,&endptr,0);
        if (v > 0xff) {
            printf("define line %u: DW_OP number value "
                "unreasonable. %lu\n",
                linenum,v);
            exit(EXIT_FAILURE);
        }
        if (v == 0 && endptr == numstart) {
            printf("define line %u of %s: number value missing.\n",
                linenum,path);
            printf("Leaving a space as in #define A B 3"
                " in dwarf.h.in will cause this.\n");
            exit(EXIT_FAILURE);
        }
        if (*endptr != ' ' && *endptr != 0) {
            unsigned char e = *endptr;
            printf("define line %u: number value terminates oddly "
                "char: %u 0x%x line %s\n",
                linenum,e,e,line);
            exit(EXIT_FAILURE);
        }
        if (!v) {
            printf("define line %u: DW_OP number value "
                "zero unreasonable.\n",
                linenum);
            exit(EXIT_FAILURE);
        }
        validate_op_listed(curdefname,v,linenum);
    }
    fclose(fin);
}

static int
havedup(int j, char **out)
{
    int i = 0;
    for (; i < OPS_USED_DUPS; ++i) {
        if (dups[i].dt_val == j) {
            *out = dups[i].dt_name1;
            return 1;
        }
    }
    return 0;
}

int main(int argc, char**argv)
{
    struct ops_table_s *op = 0;
    const char *tailpath = "/src/lib/libdwarf/dwarf.h";
    char  *path  = 0;
    int inindex  = 0;
    int outindex = 0;
    int f        = 0;
    int l        = 0;
    int c        = 0;
    int res      = 0;
    int lastop   = 0;
    unsigned len = 0;

    if (argc > 1) {
        if (argc != 3) {
            printf("Expected -f <filename> of base code path\n");
            exit(EXIT_FAILURE);
        }
        if (strcmp(argv[1],"-f")) {
            printf("Expected -f\n");
            exit(EXIT_FAILURE);
        }
        path=argv[2];
    } else {
        /* env var should be set with base path of code */
        path = getenv("DWTOPSRCDIR");
        if (!path) {
            printf("Expected environment variable "
                "DWTOPSRCDIR with path of "
                "base directory (usually called 'code')\n");
            exit(EXIT_FAILURE);
        }
    }
    len = strlen(path);
    if (len >= sizeof(pathbuf)) {
        printf(" buildopstab Input path greater length "
            "than makes any sense:"
            " Giving up\n");
        exit(EXIT_FAILURE);

    }
    safe_strcpy(pathbuf,path,sizeof(pathbuf),len);
    {
        size_t remaining =  sizeof(pathbuf) -len -1;
        size_t tailpathlen = strlen(tailpath);
        if (tailpathlen >= remaining) {
            printf(" buildopstab Input tailpath greater "
                "length fits in buf: "
                "Giving up\n");
            exit(EXIT_FAILURE);
        }
        /* Notice tailpath has a leading /  */
        safe_strcpy(pathbuf+len,(char *)tailpath,
            remaining,tailpathlen);
    }
    input_name = pathbuf;

    check_if_optabsource_complete(input_name);

    printf("/*  Generated expression ops table, "
        "do not edit. */\n");
    printf("#include \"dd_opscounttab.h\"\n");
    printf("\n");
    printf("struct dwarf_opscounttab_s _dwarf_opscounttab[] = {\n");
    for ( ;  ; ++inindex) {
        const char *opn = 0;
        int sc = 0;

        op = &optabsource[inindex];
        f = op->ot_first;
        if (!f) {
            break;
        }
        if (lastop && f < lastop) {
            printf("FAILED buildopscounttab on OP,out of sequence"
                " f=0x%x lastop=0x%x\n",
                (unsigned)f,(unsigned)lastop);
            exit(EXIT_FAILURE);
        }
        if (f == lastop) {
            /*  A duplicate, ignore here. */
            continue;
        }
        l = op->ot_last;
        c = op->ot_opcount;
        sc = op->ot_stackchange;

        while (f > outindex) {
            printf("{/* %-26s 0x%02x*/ %d, 0},\n","unused",
                outindex,-1);
            ++outindex;
        }
        if (!l) {
            res = dwarf_get_OP_name(f,&opn);
            if (res != DW_DLV_OK) {
                printf("FAILED buildopscounttab on OP 0x%x\n",
                    f);
                exit(EXIT_FAILURE);
            }
            lastop = f;

            printf("{/* %-26s 0x%02x*/ %d, %d},\n",opn,f,c,sc);
            {
                char *dup = 0;
                if (havedup(f,&dup)) {
                    printf("    /* above has alt spelling %s */",
                        dup);
                    printf("\n");
                }
            }
            ++outindex;
        } else {
            int j = f;
            for ( ; j <= l; ++j) {
                res = dwarf_get_OP_name(j,&opn);
                if (res != DW_DLV_OK) {
                    printf("FAILED buildopscounttab on OP 0x%x\n",
                        f);
                    exit(EXIT_FAILURE);
                }
                printf("{/* %-26s 0x%2x*/ %d, %d}",opn,j,c,sc);
                printf(",\n");
                {
                    /*  Should NOT happen, dups should
                        be singleton entries */
                    char *dup = 0;
                    if (havedup(j,&dup)) {
                        printf("    /* above has alt spelling %s */",
                            dup);
                        printf("\n");
                        fprintf(stderr," FAIL an entry "
                            " in a list has dup. Fix table");
                        exit(EXIT_FAILURE);
                    }
                }
                ++outindex;
                lastop = j;
            }
        }
    }
    while (outindex < DWOPS_ARRAY_SIZE) {
        printf("{/* %-26s 0x%02x*/ %d, 0},\n","unused",outindex,-1);
        ++outindex;
    }
    printf("};\n");
    return 0;
}