/* jit2.c                   Copyright (C) 2006-2007, Codemist Ltd */
/*                                                and J O'Connell */

/*
 * Helper functions for just in time compiler. A lot of this
 * is architecture-independent, but a few places are concerned with
 * the particular encoding of relative branches etc.
 */

/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */



/* Signature: 57e85a33 18-Jan-2007 */

#ifndef WIN32
#include <sys/mman.h>
#endif
#include "headers.h"
#include "bytes.h"
#include "jit.h"

/*
 * The following function is provided so that during debugging the JIT can
 * generate calls to it in the compiled code. It is then a convenient place
 * to set a debugger break-point. As can be seen, it does not do anything
 * beyond that!
 */
extern void break_here();

void break_here()
{
}

reloc_table *labels_table;
reloc_table *callers_table;
reloc_table *jumpers_table;
variables_table *vars_table;
reloc_table *disassembler_labels;

/*
 * Having a fixed-size buffer for collecting compiled code in is a temporary
 * situation!
 */
unsigned char codebuffer[8192];
unsigned long codep;

/*************************************************************************/

reloc_table *add_label_to_table(reloc_table *table,
                                char *label, unsigned long pos)
{
    /* new element added to end of list */
    reloc_table *new,*current;
    current = table; /* set current to head of list */
#ifdef JDEBUG
    /* check for any duplicate labels before adding. */
    /* labels determined by programmer so only needed while coding */
    reloc_table *tmp;
    tmp = table;
    while (tmp != NULL)
    {   if (strcmp(tmp->label,label)==0)
        {   printf("\nError in reloc table\nDuplicate entry: %s\n",label);
            return table;
        }
        else tmp = tmp->next;
    }
#endif
    /* Any elements added yet? */
/*
 * Hmmm - do I like using malloc()???
 */
    if (current == NULL)
    {   current = malloc(sizeof(reloc_table));
        current->next = table;
        table = current;
        current->label = (char*)malloc(strlen(label)+1);
        strcpy(current->label,label);
        current->pos = pos;
    }
    else
    {   /* Already elements in list */
        while (current->next != NULL) current = current->next;
        /* Now at end of linked list */
        new = malloc(sizeof(reloc_table));
        current->next = new;
        new->next = NULL;
        new->label = (char*)malloc(strlen(label)+1);
        strcpy(new->label,label);
        new->pos = pos;
    }
    return table;
}

reloc_table* add_caller_to_table(reloc_table *table,
                                 char *label, unsigned long pos)
{
    /* new element added to end of list */
    reloc_table *new, *current;
    current = table; /* set current to head of list */
    /* Any elements added yet? */
    if (current == NULL)
    {   current = malloc(sizeof(reloc_table));
        current->next = table;
        table = current;
        current->label = (char*)malloc(strlen(label)+1);
        strcpy(current->label,label);
        current->pos = pos;
    }
    else
    {   /* Already elements in list */
        while (current->next != NULL) current = current->next;
        /* Now at end of linked list */
        new = malloc(sizeof(reloc_table));
        current->next = new;
        new->next = NULL;
        new->label = (char*)malloc(strlen(label)+1);
        strcpy(new->label,label);
        new->pos = pos;
    }
    return table;
}


reloc_table* add_dis_label_to_table(reloc_table* table,
                                    char* label, unsigned long pos)
{
    /* 
     * New element added to the end of the list.
     * Allow duplicates
     */
    reloc_table *current,*new;
    current = table; /* set current to head of list */

    /* Any elements added yet? */
    if (current == NULL) 
    {   current = malloc(sizeof(reloc_table));
        current->next = table;
        table = current;
        current->label = (char*)malloc(strlen(label)+1);
        strcpy(current->label,label);
        current->pos = pos;
    }
    else
    {   /* Already elements in list */
        while (current->next != NULL) current = current->next;
        /* Now at end of linked list */
        new = malloc(sizeof(reloc_table));
        current->next = new;
        new->next = NULL;
        new->label = (char*)malloc(strlen(label)+1);
        strcpy(new->label,label);
        new->pos = pos;
    }
    return table;
}

variables_table* add_var_to_table(variables_table *table,
                                  int addr, unsigned long pos)
{
    /* 
     * New element added to the end of the list.
     * Allow duplicates
     */
    variables_table *current,*new;
    current = table; /* set current to head of list */

    /* Any elements added yet? */
    if (current == NULL)
    {   current = malloc(sizeof(variables_table));
        current->next = table;
        table = current;
        current->addr = addr;
        current->pos = pos;
    }
    else
    {   /* Already elements in list */
        while (current->next != NULL) current = current->next;
        /* Now at end of linked list */
        new = malloc(sizeof(variables_table));
        current->next = new;
        new->next = NULL;
        new->addr = addr;
        new->pos = pos;
    }
    return table;
}

void startcode()
{
    codep=0;
}

void putbyte(int c)
{
    /* check for buffer overflow */
    codebuffer[codep++]=c;
}

/*
 * For the next few, consider endian-ness of the target machine.
 */

void put2bytes(int c)
{
    putbyte((c>>8) & 0xff);
    putbyte(c & 0xff);
}

void put3bytes(int c)
{
    putbyte(c>>16 & 0xff);
    putbyte((c>>8) & 0xff);
    putbyte(c & 0xff);
}

void put4bytes(int c)
{
    putbyte((c>>24) & 0xff);
    putbyte((c>>16) & 0xff);
    putbyte((c>>8) & 0xff);
    putbyte(c & 0xff);
}

/*
 * Do I want to rely on a C compiler having "long long" as a 64-bit type?
 */
void put5bytes(long long c)
{
    putbyte((c>>32) & 0xff);
    putbyte((c>>24) & 0xff);
    putbyte((c>>16) & 0xff);
    putbyte((c>>8) & 0xff);
    putbyte(c & 0xff);
}

void put_little_endian(Lisp_Object c)
{
    putbyte(c & 0xff);
    putbyte((c>>8) & 0xff);
    putbyte((c>>16) & 0xff);
    putbyte((c>>24) & 0xff);
}

void put_addr(void *c)
{
    /* 
     * Store the variable's address in the reloc table.  
     * Address will be put into the code at linkage.
     * Doing it at the end allows for peephole optimisations.  
     * If it was added now that would mess that up.
     * NB the case "void *" to "int" here that is only going to
     * be valid on 32-bit machines.
     */
    vars_table = add_var_to_table(vars_table, (int)c, codep);
    put4bytes(0x00000000);
}

int isreg(int reg)
{
    /* return true if it is a register */
    if(0x0 <= reg && reg <= 0x7) return 1;
    else return 0;     /* else it is a memory location */
}

void call_rel_fn(char *label)
{
    /* all calls are a relative 32bit signed offset */
    putbyte(0xe8); /* call */
    callers_table = add_caller_to_table(callers_table, label, codep);
    put4bytes(0xfcffffff); /* address */
}

void jump(char *label)
{
    /* jump must be rel32 for now */
    putbyte(0xe9);
    jumpers_table = add_caller_to_table(jumpers_table, label, codep);
    put4bytes(0xffffffff);
}

void cond_jump(char *label, int cond)
{
    /* jump must be rel32 for now */
    put2bytes(cond);
    jumpers_table = add_caller_to_table(jumpers_table, label, codep);
    put4bytes(0xffffffff);
}

void add_label(char *label)
{
    labels_table = add_label_to_table(labels_table, label, codep);
}

void add_disassembler_label(char *label)
{
    disassembler_labels =
        add_dis_label_to_table(disassembler_labels, label, codep);
/* FIXME rename these fns to more generic like dupe, nodupe */
}

void Jcall_abs_fn(void *addr_big_endian)
{
    /* overwrites %eax register */
    /* Jpush(1,EDI);     preserve A_reg */
    /* push_A_reg(); */
    putbyte(0xb8);
    put_addr(addr_big_endian);
    put2bytes(0xffd0);
    /* pop_A_reg(); */
    /* Jpop(1,EDI); */
}

void free_labels_table(reloc_table *head)
{
    reloc_table *next;
    while (head != NULL)
    {   free(head->label);
        next = head->next;
        free(head);
        head = next;
    }
}

void free_var_table(variables_table *head)
{
    variables_table *next;
    while (head != NULL)
    {   next = head->next;
        free(head);
        head = next;
    }
}

void reloc_calls()
{
    reloc_table *callerlist = callers_table;
    reloc_table *labellist = labels_table;
    int found = 0;
    unsigned long caller = 0;
    unsigned long offset = 0;
    unsigned long saved_codep = codep;
    while (callerlist != NULL)
    {   labellist = labels_table;
        found = 0;
        while (labellist != NULL)
        {   if (strcmp(callerlist->label,labellist->label)==0)
            {   /* calculate relative call offset and update code */
                caller = callerlist->pos;
                /* -4 because offset starts at the next instruction following the call instruction */
                offset = labellist->pos  - callerlist->pos - 4;
                codep = caller;
                                
                /* write offset to codebuffer[codep] */
                put_little_endian(offset);
                                
                /* break from while loop */
                found = 1;
                labellist = NULL;
            }
            else labellist = labellist->next;
        }
        if (!found) printf("\nBUG IN RELOC TABLE: %s %lu\n",callerlist->label, callerlist->pos);
        callerlist = callerlist->next;
    }
    codep = saved_codep;
}

void reloc_jumps()
{
    reloc_table *jumperlist = jumpers_table;
    reloc_table *labellist = labels_table;
    int found = 0;
    unsigned long jumper = 0;
    unsigned long offset = 0;
    unsigned long saved_codep = codep;
    while (jumperlist != NULL)
    {   labellist = labels_table;
        found = 0;
        while (labellist != NULL)
        {   if (strcmp(jumperlist->label,labellist->label)==0)
            {   /* -4 because offset is from next instruction */
                /* calculate relative jump offset and update code */
                jumper = jumperlist->pos;
                offset = labellist->pos  - jumperlist->pos - 4;
                codep = jumper;
                
                /* write offset to codebuffer[codep] */
                /* put4bytes(offset); */
                put_little_endian(offset);
                                
                /* break from while loop */
                found = 1;
                labellist = NULL;
            }
            else labellist = labellist->next;
        }
        if (!found) printf("\nBUG IN RELOC TABLE: %s %lu\n",jumperlist->label,jumperlist->pos);
        jumperlist = jumperlist->next;
    }
    codep = saved_codep;
}

void reloc_vars()
{
    variables_table *varlist = vars_table;
    unsigned long saved_codep = codep;
    while(varlist != NULL)
    {   codep = varlist->pos;
        put_little_endian(varlist->addr);
        varlist = varlist->next;
    }
    codep = saved_codep;
}

/* end of jit2.c */
