/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  handle OMF fixups
*
****************************************************************************/

#include <stddef.h>

#include "globals.h"
#include "parser.h"
#include "segment.h"
#include "fixup.h"
#include "omfint.h"
#include "omfspec.h"
#include "myassert.h"

extern const char szNull[];
extern unsigned omf_GetGrpIdx( struct asym *sym );

/* logical data for fixup subrecord creation */
struct logref {
    uint_8  frame_meth;     /* see enum frame_methods in omfspec.h      */
    uint_16 frame_datum;    /* datum for certain frame methods          */
    uint_8  is_secondary;   /* can write target in a secondary manner   */
    uint_8  target_meth;    /* see enum target_methods in omfspec.h     */
    uint_16 target_datum;   /* datum for certain target methods         */
    int_32  target_offset;  /* offset of target for target method       */
};

static uint_8 *putIndex( uint_8 *p, uint_16 index )
/*************************************************/
{
    if( index > 0x7f ) {
        *p++ = 0x80 | ( index >> 8 );
    }
    *p++ = index;
    return( p );
}

static uint_8 *put16( uint_8 *p, uint_16 value )
/**********************************************/
{
    WriteU16( p, value );
    return( p + sizeof( uint_16 ) );
}

static uint_8 *put32( uint_8 *p, uint_32 value )
/**********************************************/
{
    WriteU32( p, value );
    return( p + sizeof( uint_32 ) );
}

static uint_8 *putFrameDatum( uint_8 *p, uint_8 method, uint_16 datum )
/*********************************************************************/
{
    switch( method ) {
    case FRAME_SEG:
    case FRAME_GRP:
    case FRAME_EXT:
        return( putIndex( p, datum ) );
#if 0  /* v2.12: FRAME_ABS is invalid according to TIS OMF docs. */
    case FRAME_ABS:
        return( put16( p, datum ) );
#endif
    }
    /* for FRAME_LOC & FRAME_TARG ( & FRAME_NONE ) there's no datum to write. */
    return( p );
}

static uint_8 *putTargetDatum( uint_8 *p, uint_8 method, uint_16 datum )
/**********************************************************************/
{
#if 0 /* v2.12: JWasm won't use TARGE_ABSxx; also, it's not defined for FIXUP sub-records */
    if( ( method & 0x03 ) == TARGET_ABSWD ) {
        return( put16( p, datum ) );
    }
#endif
    return( putIndex( p, datum ) );
}

/* translate logref to FIXUP subrecord ( without Locat field ).
 * fields written to buf:
 * - uint_8     Fix Data (type of frame and target)
 * - index      Frame Datum (optional, index of a SEGDEF, GRPDEF or EXTDEF)
 * - index      Target Datum (index of a SEGDEF, GRPDEF or EXTDEF)
 * - uint_16/32 Target Displacement (optional)
 *
 * type is FIX_GEN_INTEL or FIX_GEN_MS386
 * v2.12: new function OmfFixGetFixModend() replaced code in writeModend(),
 *        TranslateLogref() has become static.
 */

static unsigned TranslateLogref( const struct logref *lr, uint_8 *buf, enum fixgen_types type )
/*********************************************************************************************/
{
    uint_8  *p;
    uint_8  target;

    /**/myassert( lr != NULL );
    /**/myassert( buf != NULL );
    /**/myassert( type == FIX_GEN_INTEL || type == FIX_GEN_MS386 );

    /*
     * According to the discussion on p102 of the Intel OMF document, we
     * cannot just arbitrarily write fixups without a displacment if their
     * displacement field is 0.  So we use the is_secondary field.
     */
    target = lr->target_meth;
    if( lr->target_offset == 0 && lr->is_secondary ) {
        target |= 0x04; /* P=1 -> no displacement field */
    }
    p = buf;
    /* write the "Fix Data" field, FfffTPtt:
     * F  : 0 = frame method is defined in fff field ( F0-F5)
     *      1 = frame is defined by a thread ( won't occur here )
     * fff: frame method
     * T  : 0 = target is defined by tt
     *      1 = target is defined by thread# in tt, P is used as bit 2 for method
     * P  : 0 = target displacement field is present
     *      1 = no displacement field
     * tt : target method
     */
    *p++ = ( lr->frame_meth << 4 ) | ( target );
    p = putFrameDatum( p, lr->frame_meth, lr->frame_datum );
    p = putTargetDatum( p, target, lr->target_datum );
    if( ( target & 0x04 ) == 0 ) {
        if( type == FIX_GEN_MS386 ) {
            p = put32( p, (uint_32)lr->target_offset );
        } else {
            p = put16( p, (uint_16)lr->target_offset );
        }
    }
    return( p - buf );
}

#if 0 /* v2.11: obsolete */

static uint TranslatePhysref( const struct physref *ref, uint_8 *buf, enum fixgen_types type )
/********************************************************************************************/
{
    uint_8  *p;

    /**/myassert( ref != NULL );
    /**/myassert( buf != NULL );
    /**/myassert( type == FIX_GEN_INTEL || type == FIX_GEN_MS386 );

    p = put16( buf, ref->frame_meth );
    p = put16( p, ref->offset );
    return( p - buf );
}

/* used when the MODEND record is written.
 * is_logical is always 1 then.
 * v2.11: obsolete. TranslateLogref() is now called directly by writeModend() in omfint.c
 */

unsigned TranslateRef( const union logphys *ref, int is_logical, uint_8 *buf, enum fixgen_types type )
/****************************************************************************************************/
{
    return( is_logical ? TranslateLogref( &ref->log, buf, type ) : TranslatePhysref( &ref->phys, buf, type ) );
}

#endif

/* generate start address subfield for MODEND */

unsigned OmfFixGenFixModend( const struct fixup *fixup, uint_8 *buf, uint_32 displ, enum fixgen_types type )
/**********************************************************************************************************/
{
    struct asym *sym = fixup->sym;
    struct logref lr;

    lr.is_secondary = FALSE;
    lr.target_offset = sym->offset + displ;

    lr.frame_datum = fixup->frame_datum;

    /* symbol is always a code label (near or far), internal or external */
    /* now set Target and Frame */

    if( sym->state == SYM_EXTERNAL ) {
        DebugMsg1(("omf_write_modend(%p): fixup->frame_type/datum=%u/%u, EXTERNAL sym=%s\n",
                  fixup, fixup->frame_type, fixup->frame_datum, sym->name));

        lr.target_meth = TARGET_EXT & TARGET_WITH_DISPL;
        lr.target_datum = sym->ext_idx1;

        if( fixup->frame_type == FRAME_GRP && fixup->frame_datum == 0 ) {
            /* set the frame to the frame of the corresponding segment */
            lr.frame_datum = omf_GetGrpIdx( sym );
        }
    } else { /* SYM_INTERNAL */
        DebugMsg1(("OmfFixGenFixModend(%p): fixup->frame_type/datum=%u/%u sym->name=%s state=%X segm=%s\n",
                  fixup, fixup->frame_type, fixup->frame_datum, sym->name, sym->state, sym->segment ? sym->segment->name : "NULL" ));
        /**/myassert( sym->state == SYM_INTERNAL );

        lr.target_meth = TARGET_SEG & TARGET_WITH_DISPL;
        lr.target_datum = GetSegIdx( sym->segment );
    }

    if( fixup->frame_type != FRAME_NONE && fixup->frame_type != FRAME_SEG ) {
        lr.frame_meth = (uint_8)fixup->frame_type;
    } else {
        lr.frame_meth = FRAME_TARG;
    }
    return( TranslateLogref( &lr, buf, type ) );
}

//#define IsPC( sym ) ( sym->name_size == 1  && sym->name[0] == '$' )

/* translate a fixup to a logref */

static int omf_set_logref( const struct fixup *fixup, struct logref *lr )
/***********************************************************************/
{
    struct asym      *sym;

    sym = fixup->sym; /* may be NULL! */

    DebugMsg1(("omf_set_logref: sym=%s, state=%d, fixup->type=%u, fixup->frame_type/datum=%u/%u\n",
               sym ? sym->name : "NULL", sym ? sym->state : -1, fixup->type, fixup->frame_type, fixup->frame_datum ));

    /*------------------------------------*/
    /* Determine the Target and the Frame */
    /*------------------------------------*/

    if( sym == NULL ) {

        DebugMsg1(("omf_set_logref: sym=NULL, frame_type=%u\n", fixup->frame_type ));
        if ( fixup->frame_type == FRAME_NONE ) /* v1.96: nothing to do without a frame */
            return( 0 );
        /* v2.15: do NOT create fixups for FLAT group frame.
         * Perhaps never create fixups for GROUP frames? It's useless
         * unless the linker does pack groups. And, last but not least, jwlink crashes
         * if target datum contains FLAT group index.
         */
        if ( ModuleInfo.flat_grp && fixup->frame_type == FRAME_GRP &&
            fixup->frame_datum == ModuleInfo.flat_grp->e.grpinfo->grp_idx )
            return( 0 );
        lr->target_meth = fixup->frame_type;
        lr->target_datum = fixup->frame_datum;
        lr->frame_meth = FRAME_TARG;
#if 1
        /* v2.15: modify frame if segment (used in override) is in a group; see lea2.asm.
         *       note that this mod also affects test offset12.asm!
         */
        if ( fixup->frame_type == FRAME_SEG ) {
            struct dsym *seg;
            for( seg = SymTables[TAB_SEG].head; seg; seg = seg->next ) {
                if ( seg->e.seginfo->seg_idx == fixup->frame_datum ) {
                    if ( seg->e.seginfo->group ) {
                        lr->frame_meth = FRAME_GRP;
                        lr->frame_datum = ((struct dsym *)(seg->e.seginfo->group))->e.grpinfo->grp_idx;
                    }
                    break;
                }
            }
        }
#endif
    } else if( sym->state == SYM_UNDEFINED ) { /* shouldn't happen */

        DebugMsg(("omf_set_logref: sym->state is SYM_UNDEFINED\n" ));
        EmitErr( SYMBOL_NOT_DEFINED, sym->name );
        return( 0 );

    } else if( sym->state == SYM_GRP ) {

        DebugMsg1(("omf_set_logref: sym->state is SYM_GRP\n" ));
        lr->target_meth = TARGET_GRP;
        lr->target_datum = ((struct dsym *)sym)->e.grpinfo->grp_idx;
        if( fixup->frame_type != FRAME_NONE ) {
            lr->frame_meth = fixup->frame_type;
            lr->frame_datum = fixup->frame_datum;
        } else {
            lr->frame_meth = FRAME_GRP;
            lr->frame_datum = lr->target_datum;
        }

    } else if( sym->state == SYM_SEG ) {

        DebugMsg1(("omf_set_logref: sym->state is SYM_SEG %s\n" ));
        lr->target_meth = TARGET_SEG;
        lr->target_datum = GetSegIdx( sym );
        if( fixup->frame_type != FRAME_NONE ) {
            lr->frame_meth = fixup->frame_type;
            lr->frame_datum = fixup->frame_datum;
        } else {
            lr->frame_meth = FRAME_SEG;
            lr->frame_datum = lr->target_datum;
        }

    } else {

        /* symbol is a label */

        lr->frame_datum = fixup->frame_datum;
        if( sym->state == SYM_EXTERNAL ) {
            DebugMsg1(("omf_set_logref: sym->state is SYM_EXTERNAL, fixup->frame_type/datum=%u/%u\n",
                      fixup->frame_type, fixup->frame_datum ));
            lr->target_meth = TARGET_EXT;
            lr->target_datum = sym->ext_idx1;

            if( fixup->frame_type == FRAME_GRP && fixup->frame_datum == 0 ) {
                /* set the frame to the frame of the corresponding segment */
                lr->frame_datum = omf_GetGrpIdx( sym );
            }
        } else {
            /* must be SYM_INTERNAL */
            /**/myassert( sym->state == SYM_INTERNAL );
            DebugMsg1(("omf_set_logref: sym->state is SYM_INTERNAL, sym->segment=%s\n",
                       sym->segment ? sym->segment->name : "NULL" ));
#ifdef DEBUG_OUT
            if ( sym->segment && ((struct dsym *)sym->segment)->e.seginfo->group )
                DebugMsg1(("omf_seg_logref: group=%s\n", ((struct dsym *)sym->segment)->e.seginfo->group->name ));
#endif
            /* v2.08: don't use info from assembly-time variables */
            /* v2.16: the '$' is NO assembly-time variable; however, flag "isvariable" is TRUE!
             *        Are assembly-time variables defined by '=' supposed to create a fixup at all???
             *        Why should their (final) value be defined at link time???
             */
            //if ( sym->isvariable ) {
            if ( sym->isvariable && sym->isequate ) {
                lr->target_meth = ( fixup->frame_type == FRAME_GRP ? TARGET_GRP : TARGET_SEG );
                lr->target_datum = fixup->frame_datum;
                DebugMsg1(("omf_seg_logref: assembly-time variable branch\n" ));
            } else if ( sym->segment == NULL ) { /* shouldn't happen */
                EmitErr( SEGMENT_MISSING_FOR_FIXUP, sym->name );
                return ( 0 );
#if COMDATSUPP
            } else if ( ( (struct dsym *)sym->segment)->e.seginfo->comdat_selection ) {
                DebugMsg1(("omf_set_logref: comdat selection\n" ));
                lr->target_meth = TARGET_EXT;
                lr->target_datum = ((struct dsym *)sym->segment)->e.seginfo->seg_idx;
                lr->frame_meth = FRAME_TARG;
                return( 1 );
#endif
            } else {
                lr->target_meth = TARGET_SEG;
                lr->target_datum = GetSegIdx( sym->segment );
            }
        }

        if( fixup->frame_type != FRAME_NONE ) {  /* types: 0=SEG,1=GRP ..., 6=NONE [internal only] */
            lr->frame_meth = (uint_8)fixup->frame_type;
        } else {
            lr->frame_meth = FRAME_TARG;
        }
    }

    /*
     * Optimize the fixup
     * pairs: FRAME_SEG(0) <-> TARGET_SEG(4)
     *        FRAME_GRP(1) <-> TARGET_GRP(5)
     *        FRAME_EXT(2) <-> TARGET_EXT(6)
     * will be optimized.
    */

    if( lr->frame_meth == ( lr->target_meth - TARGET_SEG ) ) {
        DebugMsg1(("omf_set_logref: fixup optimized, frame_meth=%u, target_meth=%u\n", lr->frame_meth, lr->target_meth ));
        lr->frame_meth = FRAME_TARG; /* frame same as target */
    }

    return( 1 );
}

/* translate a fixup into its binary representation,
 * which is a "FIXUPP subrecord" according to OMF docs.
 * structure:
 * - WORD, Locat: 1MLLLLDD DDDDDDDD, is
 *   1 = indicates FIXUP, no THREAD subrecord
 *   M = mode: 1=segment relative, 0=self relative
 *   L = location, see LOC_ entries in omfspec.h
 *   D = data record offset, 10 bits for range 0-3FFh
 * - BYTE, Fix Data: FRRRTPGG, is
 *   F = 0=frame defined in fixup, 1=frame defined in frame thread
 *   R = Frame
 *   T = 0=target defined in fixup, 1=Target defined in target thread
 *   P = 0=target displacement is present
 *   G = lower bits of target method (F=0), target thread number (F=1)
 * - void/BYTE/WORD, Frame Datum
 * - BYTE/WORD, Target Datum
 * - WORD/DWORD, Target Displacement
 */

unsigned OmfFixGenFix( const struct fixup *fixup, uint_32 start_loc, uint_8 *buf, enum fixgen_types type )
/********************************************************************************************************/
{
    uint_8  locat1;
    uint_8  self_relative = FALSE;
    unsigned data_rec_offset;
    struct logref lr;

    /**/myassert( fixup != NULL );
    /**/myassert( buf != NULL );
    /**/myassert( type == FIX_GEN_INTEL || type == FIX_GEN_MS386 );

    lr.is_secondary = TRUE;
    lr.target_offset = 0;

    switch( fixup->type ) {
    case FIX_RELOFF8:
        self_relative = TRUE;
        /* no break */
    case FIX_OFF8:
        locat1 = ( LOC_OFFSET_LO << 2 );
        break;
    case FIX_RELOFF16:
        self_relative = TRUE;
        /* no break */
    case FIX_OFF16:
        locat1 = ( fixup->loader_resolved ? LOC_MS_LINK_OFFSET << 2 : LOC_OFFSET << 2 );
        break;
    case FIX_RELOFF32:
        self_relative = TRUE;
        /* no break */
    case FIX_OFF32:
        locat1 = ( fixup->loader_resolved ? LOC_MS_LINK_OFFSET_32 << 2 : LOC_MS_OFFSET_32 << 2 );
        break;
    case FIX_HIBYTE:
        locat1 = ( LOC_OFFSET_HI << 2 );
        break;
    case FIX_SEG:
        locat1 = ( LOC_BASE << 2 );
        break;
    case FIX_PTR16:
        locat1 = ( LOC_BASE_OFFSET << 2 );
        break;
    case FIX_PTR32:
        locat1 = ( LOC_MS_BASE_OFFSET_32 << 2 );
        break;
    default: /* shouldn't happen. Check for valid fixup has already happened */
        EmitErr( UNSUPPORTED_FIXUP_TYPE,
               ModuleInfo.fmtopt->formatname,
               fixup->sym ? fixup->sym->name : szNull );
        return( 0 );
    }
    locat1 |= self_relative ? 0x80 : 0xc0; /* bit 7: 1=is a fixup subrecord */

    if ( omf_set_logref( fixup, &lr ) == 0 )
        return( 0 );

    /* magnitude of fixup's position is 10! */
    /**/myassert( fixup->locofs - start_loc < 1024 );

    /* calculate the fixup's position in current LEDATA */
    data_rec_offset = fixup->locofs - start_loc;

    locat1 |= data_rec_offset >> 8;
    *buf = locat1;
    *(buf+1) = (uint_8)data_rec_offset;
    return( 2 + TranslateLogref( &lr, buf+2, type ) );
}

