/* ------------------------------------------------------------------------- */
/*   "bpatch" : Keeps track of, and finally acts on, backpatch markers,      */
/*              correcting symbol values not known at compilation time       */
/*                                                                           */
/*   Part of Inform 6.42                                                     */
/*   copyright (c) Graham Nelson 1993 - 2024                                 */
/*                                                                           */
/* ------------------------------------------------------------------------- */

#include "header.h"

uchar *staticarray_backpatch_table; /* Allocated to staticarray_backpatch_size */
memory_list staticarray_backpatch_table_memlist;
uchar *zmachine_backpatch_table; /* Allocated to zmachine_backpatch_size */
memory_list zmachine_backpatch_table_memlist;
uchar *zcode_backpatch_table; /* Allocated to zcode_backpatch_size */
memory_list zcode_backpatch_table_memlist;
int32 zcode_backpatch_size, staticarray_backpatch_size,
    zmachine_backpatch_size;

/* ------------------------------------------------------------------------- */
/*   Marker values                                                           */
/* ------------------------------------------------------------------------- */

extern char *describe_mv(int mval)
{   switch(mval)
    {   case NULL_MV:       return("null");

        /*  Marker values used in ordinary story file backpatching  */

        case DWORD_MV:      return("dictionary word");
        case STRING_MV:     return("string literal");
        case INCON_MV:      return("system constant");
        case IROUTINE_MV:   return("routine");
        case VROUTINE_MV:   return("veneer routine");
        case ARRAY_MV:      return("internal array");
        case STATIC_ARRAY_MV:  return("internal static array");
        case NO_OBJS_MV:    return("the number of objects");
        case INHERIT_MV:    return("inherited common p value");
        case INDIVPT_MV:    return("indiv prop table address");
        case INHERIT_INDIV_MV: return("inherited indiv p value");
        case MAIN_MV:       return("ref to Main");
        case SYMBOL_MV:     return("ref to symbol value");

        /*  Additional marker values used in Glulx backpatching
            (IDENT_MV is not really used at all any more) */

        case VARIABLE_MV:   return("global variable");
        case IDENT_MV:      return("prop identifier number");
        case ACTION_MV:     return("action");
        case OBJECT_MV:     return("internal object");

        /* Only occurs secondary to another reported error */
        case ERROR_MV:      return("error");

    }
    return("** No such MV **");
}

extern char *describe_mv_short(int mval)
{   switch(mval)
    {   case NULL_MV:       return("");

        /*  Marker values used in ordinary story file backpatching  */

        case DWORD_MV:      return("dict");
        case STRING_MV:     return("str");
        case INCON_MV:      return("syscon");
        case IROUTINE_MV:   return("rtn");
        case VROUTINE_MV:   return("vrtn");
        case ARRAY_MV:      return("arr");
        case STATIC_ARRAY_MV:  return("stat-arr");
        case NO_OBJS_MV:    return("obj-count");
        case INHERIT_MV:    return("inh-com");
        case INDIVPT_MV:    return("indiv-ptab");
        case INHERIT_INDIV_MV: return("inh-indiv");
        case MAIN_MV:       return("main");
        case SYMBOL_MV:     return("sym");

        /*  Additional marker values used in Glulx backpatching
            (IDENT_MV is not really used at all any more) */

        case VARIABLE_MV:   return("glob");
        case IDENT_MV:      return("prop");
        case ACTION_MV:     return("action");
        case OBJECT_MV:     return("obj");

        case LABEL_MV:      return("lbl");
        case DELETED_MV:    return("del");

        /* Only occurs secondary to another reported error */
        case ERROR_MV:      return("err");

    }
    if (mval >= BRANCH_MV && mval < BRANCHMAX_MV) return "br";
    
    return("???");
}

/* ------------------------------------------------------------------------- */
/*   The mending operation                                                   */
/* ------------------------------------------------------------------------- */

int backpatch_marker, backpatch_size, backpatch_error_flag;

static int32 backpatch_value_z(int32 value)
{   /*  Corrects the quantity "value" according to backpatch_marker  */

    ASSERT_ZCODE();

    if (bpatch_trace_setting)
        printf("BP %s applied to %04x giving ",
            describe_mv(backpatch_marker), value);

    switch(backpatch_marker)
    {   case STRING_MV:
            value += strings_offset/scale_factor; break;
        case ARRAY_MV:
            value += variables_offset; break;
        case STATIC_ARRAY_MV:
            value += static_arrays_offset; break;
        case IROUTINE_MV:
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset/scale_factor;
            break;
        case VROUTINE_MV:
            if ((value<0) || (value>=VENEER_ROUTINES))
            {
                if (compiler_error
                    ("Backpatch veneer routine number out of range"))
                {   printf("Illegal BP veneer routine number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            value = veneer_routine_address[value]; 
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset/scale_factor;
            break;
        case NO_OBJS_MV:
            value = no_objects; break;
        case INCON_MV:
            if ((value<0) || (value>=NO_SYSTEM_CONSTANTS))
            {
                if (compiler_error
                    ("Backpatch system constant number out of range"))
                {   printf("Illegal BP system constant number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            value = value_of_system_constant(value); break;
        case DWORD_MV:
            value = dictionary_offset + 7 +
                    final_dict_order[value]*(DICT_ENTRY_BYTE_LENGTH);
            break;
        case ACTION_MV:
            break;
        case INHERIT_MV:
            value = 256*zmachine_paged_memory[value + prop_values_offset]
                    + zmachine_paged_memory[value + prop_values_offset + 1];
            break;
        case INHERIT_INDIV_MV:
            value = 256*zmachine_paged_memory[value
                        + individuals_offset]
                    + zmachine_paged_memory[value
                        + individuals_offset + 1];
            break;
        case INDIVPT_MV:
            value += individuals_offset;
            break;
        case MAIN_MV:
            value = get_symbol_index("Main");
            if (value < 0 || (symbols[value].flags & UNKNOWN_SFLAG)) {
                error("No 'Main' routine has been defined");
                value = 0;
                break;
            }
            if (symbols[value].type != ROUTINE_T) {
                ebf_symbol_error("'Main' routine", symbols[value].name, typename(symbols[value].type), symbols[value].line);
                value = 0;
                break;
            }
            symbols[value].flags |= USED_SFLAG;
            value = symbols[value].value;
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset/scale_factor;
            break;
        case SYMBOL_MV:
            if ((value<0) || (value>=no_symbols))
            {
                if (compiler_error("Backpatch symbol number out of range"))
                {   printf("Illegal BP symbol number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            if (symbols[value].flags & UNKNOWN_SFLAG)
            {   if (!(symbols[value].flags & UERROR_SFLAG))
                {   symbols[value].flags |= UERROR_SFLAG;
                    error_named_at("No such constant as",
                        symbols[value].name, symbols[value].line);
                }
            }
            else
            if (symbols[value].flags & CHANGE_SFLAG)
            {   symbols[value].flags &= (~(CHANGE_SFLAG));
                backpatch_marker = (symbols[value].marker);
                if ((backpatch_marker < 0)
                    || (backpatch_marker > LARGEST_BPATCH_MV))
                {
                    compiler_error_named(
                        "Illegal backpatch marker attached to symbol",
                        symbols[value].name);
                    backpatch_error_flag = TRUE;
                }
                else
                    symbols[value].value = backpatch_value_z((symbols[value].value) % 0x10000);
            }

            symbols[value].flags |= USED_SFLAG;
            {   int t = symbols[value].type;
                value = symbols[value].value;
                switch(t)
                {   case ROUTINE_T: 
                        if (OMIT_UNUSED_ROUTINES)
                            value = df_stripped_address_for_address(value);
                        value += code_offset/scale_factor; 
                        break;
                    case ARRAY_T: value += variables_offset; break;
                    case STATIC_ARRAY_T: value += static_arrays_offset; break;
                }
            }
            break;
        default:
            if (compiler_error("Illegal backpatch marker"))
            {   printf("Illegal backpatch marker %d value %04x\n",
                    backpatch_marker, value);
                backpatch_error_flag = TRUE;
            }
            break;
    }

    if (bpatch_trace_setting) printf(" %04x\n", value);

    return(value);
}

static int32 backpatch_value_g(int32 value)
{   /*  Corrects the quantity "value" according to backpatch_marker  */
    int32 valaddr;

    ASSERT_GLULX();

    if (bpatch_trace_setting)
        printf("BP %s applied to %04x giving ",
            describe_mv(backpatch_marker), value);

    switch(backpatch_marker)
    {
        case STRING_MV:
            if (value <= 0 || value > no_strings)
              compiler_error("Illegal string marker.");
            value = strings_offset + compressed_offsets[value-1]; break;
        case IROUTINE_MV:
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset;
            break;
        case ARRAY_MV:
            value += arrays_offset; break;
        case STATIC_ARRAY_MV:
            value += static_arrays_offset; break;
        case VARIABLE_MV:
            value = variables_offset + (4*value); break;
        case OBJECT_MV:
            value = object_tree_offset + (OBJECT_BYTE_LENGTH*(value-1)); 
            break;
        case VROUTINE_MV:
            if ((value<0) || (value>=VENEER_ROUTINES))
            {
                if (compiler_error
                    ("Backpatch veneer routine number out of range"))
                {   printf("Illegal BP veneer routine number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            value = veneer_routine_address[value];
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset;
            break;
        case NO_OBJS_MV:
            value = no_objects; break;
        case INCON_MV:
            if ((value<0) || (value>=NO_SYSTEM_CONSTANTS))
            {
                if (compiler_error
                    ("Backpatch system constant number out of range"))
                {   printf("Illegal BP system constant number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            value = value_of_system_constant(value); break;
        case DWORD_MV:
            value = dictionary_offset + 4 
              + final_dict_order[value]*DICT_ENTRY_BYTE_LENGTH;
            break;
        case ACTION_MV:
            break;
        case INHERIT_MV:
            valaddr = (prop_values_offset - Write_RAM_At) + value;
            value = ReadInt32(zmachine_paged_memory + valaddr);
            break;
        case INHERIT_INDIV_MV:
            error("*** No individual property storage in Glulx ***");
            break;
        case INDIVPT_MV:
            value += individuals_offset;
            break;
        case MAIN_MV:
            value = get_symbol_index("Main");
            if (value < 0 || (symbols[value].flags & UNKNOWN_SFLAG)) {
                error("No 'Main' routine has been defined");
                value = 0;
                break;
            }
            if (symbols[value].type != ROUTINE_T) {
                ebf_symbol_error("'Main' routine", symbols[value].name, typename(symbols[value].type), symbols[value].line);
                value = 0;
                break;
            }
            symbols[value].flags |= USED_SFLAG;
            value = symbols[value].value;
            if (OMIT_UNUSED_ROUTINES)
                value = df_stripped_address_for_address(value);
            value += code_offset;
            break;
        case SYMBOL_MV:
            if ((value<0) || (value>=no_symbols))
            {
                if (compiler_error("Backpatch symbol number out of range"))
                {   printf("Illegal BP symbol number: %d\n", value);
                    backpatch_error_flag = TRUE;
                }
                value = 0;
                break;
            }
            if (symbols[value].flags & UNKNOWN_SFLAG)
            {   if (!(symbols[value].flags & UERROR_SFLAG))
                {   symbols[value].flags |= UERROR_SFLAG;
                    error_named_at("No such constant as",
                        symbols[value].name, symbols[value].line);
                }
            }
            else
            if (symbols[value].flags & CHANGE_SFLAG)
            {   symbols[value].flags &= (~(CHANGE_SFLAG));
                backpatch_marker = symbols[value].marker;
                if ((backpatch_marker < 0)
                    || (backpatch_marker > LARGEST_BPATCH_MV))
                {
                    compiler_error_named(
                        "Illegal backpatch marker attached to symbol",
                        symbols[value].name);
                    backpatch_error_flag = TRUE;
                }
                else
                    symbols[value].value = backpatch_value_g(symbols[value].value);
            }

            symbols[value].flags |= USED_SFLAG;
            {   int t = symbols[value].type;
                value = symbols[value].value;
                switch(t)
                {
                    case ROUTINE_T:
                        if (OMIT_UNUSED_ROUTINES)
                            value = df_stripped_address_for_address(value);
                        value += code_offset;
                        break;
                    case ARRAY_T: value += arrays_offset; break;
                    case STATIC_ARRAY_T: value += static_arrays_offset; break;
                    case OBJECT_T:
                    case CLASS_T:
                      value = object_tree_offset + 
                        (OBJECT_BYTE_LENGTH*(value-1)); 
                      break;
                    case ATTRIBUTE_T:
                      /* value is unchanged */
                      break;
                    case CONSTANT_T:
                    case INDIVIDUAL_PROPERTY_T:
                    case PROPERTY_T:
                      /* value is unchanged */
                      break;
                    default:
                      error("*** Illegal backpatch marker in forward-declared \
symbol");
                      break;
                }
            }
            break;
        default:
            if (compiler_error("Illegal backpatch marker"))
            {   printf("Illegal backpatch marker %d value %04x\n",
                    backpatch_marker, value);
                backpatch_error_flag = TRUE;
            }
            break;
    }

    if (bpatch_trace_setting) printf(" %04x\n", value);

    return(value);
}

extern int32 backpatch_value(int32 value)
{
  if (!glulx_mode)
    return backpatch_value_z(value);
  else
    return backpatch_value_g(value);
}

static void backpatch_zmachine_z(int mv, int zmachine_area, int32 offset)
{   
    if (mv == OBJECT_MV) return;
    if (mv == IDENT_MV) return;
    if (mv == ACTION_MV) return;

    if (bpatch_trace_setting >= 2)
        printf("BP added: MV %d ZA %d Off %04x\n", mv, zmachine_area, offset);

    ensure_memory_list_available(&zmachine_backpatch_table_memlist, zmachine_backpatch_size+4);
    zmachine_backpatch_table[zmachine_backpatch_size++] = mv;
    zmachine_backpatch_table[zmachine_backpatch_size++] = zmachine_area;
    zmachine_backpatch_table[zmachine_backpatch_size++] = offset/256;
    zmachine_backpatch_table[zmachine_backpatch_size++] = offset%256;
}

static void backpatch_zmachine_g(int mv, int zmachine_area, int32 offset)
{   
    if (mv == IDENT_MV) return;
    if (mv == ACTION_MV) return;

/* The backpatch table format for Glulx:
   First, the marker byte.
   Then, the zmachine area being patched.
   Then the four-byte address.
*/

    if (bpatch_trace_setting >= 2)
        printf("BP added: MV %d ZA %d Off %06x\n", mv, zmachine_area, offset);

    ensure_memory_list_available(&zmachine_backpatch_table_memlist, zmachine_backpatch_size+6);
    zmachine_backpatch_table[zmachine_backpatch_size++] = mv;
    zmachine_backpatch_table[zmachine_backpatch_size++] = zmachine_area;
    zmachine_backpatch_table[zmachine_backpatch_size++] = (offset >> 24) & 0xFF;
    zmachine_backpatch_table[zmachine_backpatch_size++] = (offset >> 16) & 0xFF;
    zmachine_backpatch_table[zmachine_backpatch_size++] = (offset >> 8) & 0xFF;
    zmachine_backpatch_table[zmachine_backpatch_size++] = (offset) & 0xFF;
}

extern void backpatch_zmachine(int mv, int zmachine_area, int32 offset)
{
  if (!glulx_mode)
    backpatch_zmachine_z(mv, zmachine_area, offset);
  else
    backpatch_zmachine_g(mv, zmachine_area, offset);
}

extern void backpatch_zmachine_image_z(void)
{   int bm = 0, zmachine_area; int32 offset, value, addr = 0;
    ASSERT_ZCODE();
    backpatch_error_flag = FALSE;
    while (bm < zmachine_backpatch_size)
    {   backpatch_marker
            = zmachine_backpatch_table[bm];
        zmachine_area
            = zmachine_backpatch_table[bm+1];
        offset
          = 256*zmachine_backpatch_table[bm+2]
            + zmachine_backpatch_table[bm+3];
        bm += 4;

        switch(zmachine_area)
        {   case PROP_DEFAULTS_ZA:   addr = prop_defaults_offset; break;
            case PROP_ZA:            addr = prop_values_offset; break;
            case INDIVIDUAL_PROP_ZA: addr = individuals_offset; break;
            case DYNAMIC_ARRAY_ZA:   addr = variables_offset; break;
            case STATIC_ARRAY_ZA:    addr = static_arrays_offset; break;
            default:
                if (compiler_error("Illegal area to backpatch"))
                    backpatch_error_flag = TRUE;
        }
        addr += offset;

        value = 256*zmachine_paged_memory[addr]
                + zmachine_paged_memory[addr+1];
        value = backpatch_value_z(value);
        zmachine_paged_memory[addr] = value/256;
        zmachine_paged_memory[addr+1] = value%256;

        if (backpatch_error_flag)
        {   backpatch_error_flag = FALSE;
            printf("*** MV %d ZA %d Off %04x ***\n",
                backpatch_marker, zmachine_area, offset);
        }
    }
}

extern void backpatch_zmachine_image_g(void)
{   int bm = 0, zmachine_area; int32 offset, value, addr = 0;
    ASSERT_GLULX();
    backpatch_error_flag = FALSE;
    while (bm < zmachine_backpatch_size)
    {   backpatch_marker
            = zmachine_backpatch_table[bm];
        zmachine_area
            = zmachine_backpatch_table[bm+1];
        offset = zmachine_backpatch_table[bm+2];
        offset = (offset << 8) |
          zmachine_backpatch_table[bm+3];
        offset = (offset << 8) |
          zmachine_backpatch_table[bm+4];
        offset = (offset << 8) |
          zmachine_backpatch_table[bm+5];
        bm += 6;

            switch(zmachine_area) {   
        case PROP_DEFAULTS_ZA:   addr = prop_defaults_offset+4; break;
        case PROP_ZA:            addr = prop_values_offset; break;
        case INDIVIDUAL_PROP_ZA: addr = individuals_offset; break;
        case DYNAMIC_ARRAY_ZA:   addr = arrays_offset; break;
        case GLOBALVAR_ZA:       addr = variables_offset; break;
        /* STATIC_ARRAY_ZA is in ROM and therefore not handled here */
        default:
            if (compiler_error("Illegal area to backpatch"))
              backpatch_error_flag = TRUE;
        }
        addr = addr + offset - Write_RAM_At;

        value = (zmachine_paged_memory[addr] << 24)
                | (zmachine_paged_memory[addr+1] << 16)
                | (zmachine_paged_memory[addr+2] << 8)
                | (zmachine_paged_memory[addr+3]);
        value = backpatch_value_g(value);
        zmachine_paged_memory[addr] = (value >> 24) & 0xFF;
        zmachine_paged_memory[addr+1] = (value >> 16) & 0xFF;
        zmachine_paged_memory[addr+2] = (value >> 8) & 0xFF;
        zmachine_paged_memory[addr+3] = (value) & 0xFF;

        if (backpatch_error_flag)
        {   backpatch_error_flag = FALSE;
            printf("*** MV %d ZA %d Off %04x ***\n",
                backpatch_marker, zmachine_area, offset);
        }
    }
}

/* ========================================================================= */
/*   Data structure management routines                                      */
/* ------------------------------------------------------------------------- */

extern void init_bpatch_vars(void)
{   zcode_backpatch_table = NULL;
    staticarray_backpatch_table = NULL;
    zmachine_backpatch_table = NULL;
}

extern void bpatch_begin_pass(void)
{   zcode_backpatch_size = 0;
    staticarray_backpatch_size = 0;
    zmachine_backpatch_size = 0;
}

extern void bpatch_allocate_arrays(void)
{
    initialise_memory_list(&zcode_backpatch_table_memlist,
        sizeof(uchar), 128, (void**)&zcode_backpatch_table,
        "code backpatch table");
    initialise_memory_list(&staticarray_backpatch_table_memlist,
        sizeof(uchar), 128, (void**)&staticarray_backpatch_table,
        "static array backpatch table");
    initialise_memory_list(&zmachine_backpatch_table_memlist,
        sizeof(uchar), 128, (void**)&zmachine_backpatch_table,
        "machine backpatch table");
}

extern void bpatch_free_arrays(void)
{   deallocate_memory_list(&zcode_backpatch_table_memlist);
    deallocate_memory_list(&staticarray_backpatch_table_memlist);
    deallocate_memory_list(&zmachine_backpatch_table_memlist);
}

/* ========================================================================= */
