/****************************************************************************
*
*                            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:  backpatch: short forward jump optimization.
*
****************************************************************************/

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "fixup.h"
#include "segment.h"

/*
 * LABELOPT: short jump label optimization.
 * if this is 0, there is just the simple "fixup backpatch",
 * which cannot adjust any label offsets between the forward reference
 * and the newly defined label, resulting in more passes to be needed.
*/
#define LABELOPT 1

#if 0 /* v1.96: disabled */
#define SkipFixup() \
    fixup->nextbp = sym->fixup; \
    sym->fixup = fixup
#else
#define SkipFixup()
#endif
static void DoPatch( struct asym *sym, struct fixup *fixup )
/**********************************************************/
{
    int_32              disp;
    int_32              max_disp;
    unsigned            size;
    struct dsym         *seg;
#if LABELOPT
    struct asym         *sym2;
    struct fixup        *fixup2;
#endif

    /* all relative fixups should occure only at first pass and they signal forward references
     * they must be removed after patching or skiped ( next processed as normal fixup )
     */

    DebugMsg(("DoPatch(%u, %s): fixup sym=%s type=%u ofs=%" I32_SPEC "Xh loc=%" I32_SPEC "Xh opt=%u def_seg=%s\n",
              Parse_Pass + 1, sym->name,
              fixup->sym ? fixup->sym->name : "NULL",
              fixup->type,
              fixup->offset,
              fixup->locofs,
              fixup->option,
              fixup->def_seg ? fixup->def_seg->sym.name : "NULL" ));
    seg = GetSegm( sym );
    if( seg == NULL || fixup->def_seg != seg ) {
        /* if fixup location is in another segment, backpatch is possible, but
         * complicated and it's a pretty rare case, so nothing's done.
         */
        DebugMsg(("DoPatch: skipped due to seg incompat: %s - %s\n",
                  fixup->def_seg ? fixup->def_seg->sym.name : "NULL",
                  seg ? seg->sym.name : "NULL" ));
        SkipFixup();
        return;
    }

    if( Parse_Pass == PASS_1 ) {
        if( sym->mem_type == MT_FAR && fixup->option == OPTJ_CALL ) {
            /* convert near call to push cs + near call,
             * (only at first pass) */
            DebugMsg(("DoPatch: Phase error! caused by far call optimization\n"));
            ModuleInfo.PhaseError = TRUE;
            sym->offset++;  /* a PUSH CS will be added */
            /* todo: insert LABELOPT block here */
            OutputByte( 0 ); /* it's pass one, nothing is written */
            FreeFixup( fixup );
            return;
        //} else if( sym->mem_type == MT_NEAR ) {
        } else {
            /* forward reference, only at first pass */
            switch( fixup->type ) {
            case FIX_RELOFF32:
            case FIX_RELOFF16:
                FreeFixup( fixup );
                DebugMsg(("DoPatch: FIX_RELOFF32/FIX_RELOFF16, return\n"));
                return;
            case FIX_OFF8:  /* push <forward reference> */
                if ( fixup->option == OPTJ_PUSH ) {
                    size = 1;    /* size increases from 2 to 3/5 */
                    DebugMsg(("DoPatch: FIX_OFF8\n"));
                    goto patch;
                }
            }
        }
    }
    size = 0;
    switch( fixup->type ) {
    case FIX_RELOFF32:
        size = 2; /* will be 4 finally */
        /* fall through */
    case FIX_RELOFF16:
        size++; /* will be 2 finally */
        /* fall through */
    case FIX_RELOFF8:
        size++;
        /* calculate the displacement */
        // disp = fixup->offset + GetCurrOffset() - fixup->location - size;
        disp = fixup->offset + fixup->sym->offset - fixup->locofs - size - 1;
        max_disp = (1UL << ((size * 8)-1)) - 1;
        if( disp > max_disp || disp < (-max_disp-1) ) {
        patch:
            DebugMsg(("DoPatch(%u): Phase error, disp=%X, fixup=%s(%X), loc=%X!\n", Parse_Pass + 1, disp, fixup->sym->name, fixup->sym->offset, fixup->locofs ));
            ModuleInfo.PhaseError = TRUE;
            /* ok, the standard case is: there's a forward jump which
             * was assumed to be SHORT, but it must be NEAR instead.
             */
            switch( size ) {
            case 1:
                size = 0;
                switch( fixup->option ) {
                case OPTJ_EXPLICIT:
#if 0 /* don't display the error at the destination line! */
                    DebugMsg(("DoPatch: jump out of range, disp=%d\n", disp ));
                    EmitErr( JUMP_OUT_OF_RANGE, disp - max_disp );
#endif
                    return;
                case OPTJ_EXTEND: /* Jxx for 8086 */
                    size++;       /* will be 3/5 finally */
                    /* fall through */
                case OPTJ_JXX: /* Jxx for 386 */
                    size++;
                    /* fall through */
                default: /* normal JMP (and PUSH) */
                    // if( CodeInfo->Ofssize ) /* v1.96: don't use CodeInfo here! */
                    if( seg->e.seginfo->Ofssize )
                        size += 2; /* NEAR32 instead of NEAR16 */
                    size++;
#if LABELOPT
                    /* v2.04: if there's an ORG between src and dst, skip
                     * the optimization!
                     */
                    if ( Parse_Pass == PASS_1 ) {
                        for ( fixup2 = seg->e.seginfo->FixupList.head; fixup2; fixup2 = fixup2->nextrlc ) {
                            if ( fixup2->orgoccured ) {
                                DebugMsg(("DoPatch: ORG/ALIGN detected, optimization canceled\n" ));
                                return;
                            }
                            /* do this check after the check for ORG! */
                            if ( fixup2->locofs <= fixup->locofs )
                                break;
                        }
                    }
                    /* scan the segment's label list and adjust all labels
                     * which are between the fixup loc and the current sym.
                     * ( PROCs are NOT contained in this list because they
                     * use the <next>-field of dsym already!)
                     */
                    for ( sym2 = seg->e.seginfo->label_list; sym2; sym2 = (struct asym *)((struct dsym *)sym2)->next ) {
                        //if ( sym2 == sym )
                        //    continue;
                        /* v2.0: location is at least 1 byte too low, so
                         * use the "<=" operator instead of "<"!
                         */
                        //if ( sym2->offset < fixup->locofs )
                        if ( sym2->offset <= fixup->locofs )
                            break;
                        sym2->offset += size;
                        DebugMsg(("DoPatch(loc=%" I32_SPEC "X): sym %s, offset changed %" I32_SPEC "X -> %" I32_SPEC "X\n", fixup->locofs, sym2->name, sym2->offset - size, sym2->offset));
                    }
                    /* v2.03: also adjust fixup locations located between the
                     * label reference and the label. This should reduce the
                     * number of passes to 2 for not too complex sources.
                     */
                    if ( Parse_Pass == PASS_1 ) /* v2.04: added, just to be safe */
                    for ( fixup2 = seg->e.seginfo->FixupList.head; fixup2; fixup2 = fixup2->nextrlc ) {
                        if ( fixup2->sym == sym )
                            continue;
                        if ( fixup2->locofs <= fixup->locofs )
                            break;
                        fixup2->locofs += size;
                    }
#else
                    DebugMsg(("DoPatch: sym %s, offset changed %" I32_SPEC "X -> %" I32_SPEC "X\n", sym->name, sym->offset, sym->offset + size));
                    sym->offset += size;
#endif
                    /*  it doesn't matter what's actually "written" */
                    for ( ; size; size-- )
                        OutputByte( 0xCC );
                    break;
                }
                break;
            case 2:
            case 4:
                DebugMsg(("DoPatch: jump out of range, disp=%d\n", disp ));
                EmitWarn( 4, JUMP_OUT_OF_RANGE, disp - max_disp );
                break;
            }
        }
#ifdef DEBUG_OUT
        else
            DebugMsg(("DoPatch, loc=%" I32_SPEC "X: displacement still short: %Xh\n", fixup->locofs, disp ));
#endif
        /* v2.04: fixme: is it ok to remove the fixup?
         * it might still be needed in a later backpatch.
         */
        FreeFixup( fixup );
        break;
    default:
        DebugMsg(("DoPatch: default branch, unhandled fixup type=%u\n", fixup->type ));
        SkipFixup();
        break;
    }
    return;
}

ret_code BackPatch( struct asym *sym )
/************************************/
/*
 * patching for forward reference labels in Jmp/Call instructions;
 * called by LabelCreate(), ProcDef() and data_dir(), that is, whenever
 * a (new) label is defined. The new label is the <sym> parameter.
 * During the process, the label's offset might be changed!
 *
 * field sym->fixup is a "descending" list of forward references
 * to this symbol. These fixups are only generated during pass 1.
 */
{
    struct fixup     *fixup;
    struct fixup     *next;
#ifdef DEBUG_OUT
    uint_32 oldofs = sym->offset;
#endif

    DebugMsg1(("BackPatch(%s): location=%s:%X, bp_fixup=%p\n", sym->name, sym->segment ? sym->segment->name : "!NULL!", sym->offset, sym->bp_fixup ));

    for( fixup = sym->bp_fixup; fixup; fixup = next ) {
        next = fixup->nextbp;
        DoPatch( sym, fixup );
    }
    /* fixme: to clear field bp_fixup may cause memory leaks, since not all fixups are freed here.
     * usually no problem, because FASTMEM is true ( that is, LclFree() is a NOP ).
     * the problem is that these fixups are in 2 queues, one starts in sym.bp_fixup,
     * the other start in CurrSeg.FixupList.
     */
    sym->bp_fixup = NULL;
#ifdef DEBUG_OUT
    if ( oldofs != sym->offset )
        DebugMsg1(("BackPatch(%s) exit, new ofs=%X\n", sym->name, sym->offset ));
#endif
    return( NOT_ERROR );
}

