/****************************************************************************
*
*                            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:  handles ASSUME
*
****************************************************************************/

#include <ctype.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "segment.h"
#include "assume.h"
#include "types.h"
#include "label.h"
#include "lqueue.h"
#include "expreval.h"
#include "fastpass.h"
#include "tokenize.h"
#include "listing.h"

#if AMD64_SUPPORT
#define NUM_STDREGS 16
#else
#define NUM_STDREGS 8
#endif

/* prototypes */

/* todo: move static variables to ModuleInfo */

/* table SegAssume is for the segment registers;
 * for order see enum assume_segreg.
 */
struct assume_info SegAssumeTable[NUM_SEGREGS];

/* table StdAssume is for the standard registers;
 * order does match regno in special.h:
 * (R|E)AX=0, (R|E)CX=1, (R|E)DX=2, (R|E)BX=3
 * (R|E)SP=4, (R|E)BP=5, (R|E)SI=6, (R|E)DI=7
 * R8 .. R15.
 */
struct assume_info StdAssumeTable[NUM_STDREGS];

static struct asym *stdsym[NUM_STDREGS];

#if FASTPASS
static struct assume_info saved_SegAssumeTable[NUM_SEGREGS];
static struct assume_info saved_StdAssumeTable[NUM_STDREGS];
/* v2.05: saved type info content */
static struct stdassume_typeinfo saved_StdTypeInfo[NUM_STDREGS];
#endif

/* order to use for assume searches */
static const enum assume_segreg searchtab[] = {
    ASSUME_DS, ASSUME_SS, ASSUME_ES, ASSUME_FS, ASSUME_GS, ASSUME_CS
};

static const char szError[]   = { "ERROR" };
static const char szNothing[] = { "NOTHING" };
const char szDgroup[]  = { "DGROUP" };

void SetSegAssumeTable( void *savedstate )
/****************************************/
{
    DebugMsg(("SetSegAssumeTable\n" ));
    memcpy( &SegAssumeTable, savedstate, sizeof(SegAssumeTable) );
}
void GetSegAssumeTable( void *savedstate )
/****************************************/
{
    DebugMsg(("GetSegAssumeTable\n" ));
    memcpy( savedstate, &SegAssumeTable, sizeof(SegAssumeTable) );
}

/* unlike the segment register assumes, the
 * register assumes need more work to save/restore the
 * current status, because they have their own
 * type symbols, which may be reused.
 * this functions is also called by context.c, ContextDirective()!
 */

void SetStdAssumeTable( void *savedstate, struct stdassume_typeinfo *ti )
/***********************************************************************/
{
    int i;

    DebugMsg(("SetStdAssumeTable enter\n" ));
    memcpy( &StdAssumeTable, savedstate, sizeof(StdAssumeTable) );
    for ( i = 0; i < NUM_STDREGS; i++, ti++ ) {
        if ( StdAssumeTable[i].symbol ) {
            StdAssumeTable[i].symbol->type        = ti->type;
            StdAssumeTable[i].symbol->target_type = ti->target_type;
            StdAssumeTable[i].symbol->mem_type    = ti->mem_type;
            StdAssumeTable[i].symbol->ptr_memtype = ti->ptr_memtype;
            StdAssumeTable[i].symbol->is_ptr      = ti->is_ptr;
        }
    }
}

void GetStdAssumeTable( void *savedstate, struct stdassume_typeinfo *ti )
/***********************************************************************/
{
    int i;
    DebugMsg(("GetStdAssumeTable\n" ));
    memcpy( savedstate, &StdAssumeTable, sizeof(StdAssumeTable) );
    for ( i = 0; i < NUM_STDREGS; i++, ti++ ) {
        if ( StdAssumeTable[i].symbol ) {
            ti->type        = StdAssumeTable[i].symbol->type;
            ti->target_type = StdAssumeTable[i].symbol->target_type;
            ti->mem_type    = StdAssumeTable[i].symbol->mem_type;
            ti->ptr_memtype = StdAssumeTable[i].symbol->ptr_memtype;
            ti->is_ptr      = StdAssumeTable[i].symbol->is_ptr;
        }
    }
}

#if FASTPASS
void AssumeSaveState( void )
/**************************/
{
    GetSegAssumeTable( &saved_SegAssumeTable );
    GetStdAssumeTable( &saved_StdAssumeTable, saved_StdTypeInfo );
}
#endif

void AssumeInit( int pass ) /* pass may be -1 here! */
/*************************/
{
    int reg;

    for( reg = 0; reg < NUM_SEGREGS; reg++ ) {
        SegAssumeTable[reg].symbol = NULL;
        SegAssumeTable[reg].error = FALSE;
        SegAssumeTable[reg].is_flat = FALSE;
    }

    /* the GPR assumes are handled somewhat special by masm.
     * they aren't reset for each pass - instead they keep their value.
     */

    if ( pass <= PASS_1 ) { /* v2.10: just reset assumes in pass one */

        for( reg = 0; reg < NUM_STDREGS; reg++ ) {
            StdAssumeTable[reg].symbol = NULL;
            StdAssumeTable[reg].error = 0;
        }
        if ( pass == PASS_1 )
            memset( &stdsym, 0, sizeof( stdsym ) );
    }
#if FASTPASS
    if ( pass > PASS_1 && UseSavedState ) {
        SetSegAssumeTable( &saved_SegAssumeTable );
#if 0
        /* v2.10: this is a weird Masm feature:
         * if a GPR is assumed to be a pointer to a struct,
         * it isn't reset when a new pass starts.
         * see assume11.asm and assume12.asm
         */
        if ( pass == PASS_2 ) {
            struct assume_info *psaved = &saved_StdAssumeTable;
            struct stdassume_typeinfo *ptype = &saved_StdTypeInfo;
            for ( reg = 0; reg < NUM_STDREGS; reg++, psaved++, ptype++ )
                if ( psaved->symbol == NULL && StdAssumeTable[reg].symbol && StdAssumeTable[reg].symbol->target_type ) {
                    psaved->symbol = StdAssumeTable[reg].symbol;
                    ptype->type        = psaved->symbol->type;
                    ptype->target_type = psaved->symbol->target_type;
                    ptype->mem_type    = psaved->symbol->mem_type;
                    ptype->ptr_memtype = psaved->symbol->ptr_memtype;
                    ptype->is_ptr      = psaved->symbol->is_ptr;
                }
        }
#endif
        SetStdAssumeTable( &saved_StdAssumeTable, saved_StdTypeInfo );
    }
#endif
}

/* generate assume lines after .MODEL directive
 * model is in ModuleInfo.model, it can't be MODEL_NONE.
 */
void ModelAssumeInit( void )
/**************************/
{
    //const char *pCS;
    const char *pFSassume = szError;
    const char *pGSassume = szError;
    const char *pFmt;

    /* Generates codes for assume */
    switch( ModuleInfo.model ) {
    case MODEL_FLAT:
#if AMD64_SUPPORT
        if ( ModuleInfo.fctype == FCT_WIN64 )
            pGSassume = szNothing;
#endif
        AddLineQueueX( "%r %r:%r,%r:%r,%r:%r,%r:%r,%r:%s,%r:%s",
                  T_ASSUME, T_CS, T_FLAT, T_DS, T_FLAT, T_SS, T_FLAT, T_ES, T_FLAT, T_FS, pFSassume, T_GS, pGSassume );
        break;
    case MODEL_TINY:
    case MODEL_SMALL:
    case MODEL_COMPACT:
    case MODEL_MEDIUM:
    case MODEL_LARGE:
    case MODEL_HUGE:
        /* v2.03: no DGROUP for COFF/ELF */
#if COFF_SUPPORT || ELF_SUPPORT
        if( Options.output_format == OFORMAT_COFF
#if ELF_SUPPORT
           || Options.output_format == OFORMAT_ELF
#endif
          )
            break;
#endif
#if 0 /* v2.13: removed */
        if ( ModuleInfo.model == MODEL_TINY )
            pCS = szDgroup;
        else
            pCS = SimGetSegName( SIM_CODE );
#endif
        if ( ModuleInfo.distance != STACK_FAR )
            /* v2.13: CS assume removed */
            //pFmt = "%r %r:%s,%r:%s,%r:%s";
            pFmt = "%r %r:%s,%r:%s";
        else
            /* v2.13: CS assume removed */
            //pFmt = "%r %r:%s,%r:%s";
            pFmt = "%r %r:%s";
        /* v2.13: CS assume removed */
        //AddLineQueueX( pFmt, T_ASSUME, T_CS, pCS, T_DS, szDgroup, T_SS, szDgroup );
        AddLineQueueX( pFmt, T_ASSUME, T_DS, szDgroup, T_SS, szDgroup );
        break;
    }
}

/* used by INVOKE directive */

struct asym *GetStdAssume( int reg )
/**********************************/
{
    if ( StdAssumeTable[reg].symbol )
        if ( StdAssumeTable[reg].symbol->mem_type == MT_TYPE )
            return( StdAssumeTable[reg].symbol->type );
        else
            return( StdAssumeTable[reg].symbol->target_type );
    return ( NULL );
}

/* v2.05: new, used by
 * expression evaluator if a register is used for indirect addressing
 */

struct asym *GetStdAssumeEx( int reg )
/************************************/
{
    return( StdAssumeTable[reg].symbol );
}

ret_code AssumeDirective( int i, struct asm_tok tokenarray[] )
/************************************************************/
/* Handles ASSUME
 * syntax is :
 * - ASSUME
 * - ASSUME NOTHING
 * - ASSUME segregister : seglocation [, segregister : seglocation ]
 * - ASSUME dataregister : qualified type [, dataregister : qualified type ]
 * - ASSUME register : ERROR | NOTHING | FLAT
 */
{
    int             reg;
    int             j;
    int             size;
    uint_32         flags;
    struct assume_info *info;
    bool            segtable;
    struct qualified_type ti;

    DebugMsg1(( "AssumeDirective enter, pass=%u\n", Parse_Pass+1 ));

    for( i++; i < Token_Count; i++ ) {

        if( ( tokenarray[i].token == T_ID )
            && (0 == _stricmp( tokenarray[i].string_ptr, szNothing )) ) {
            AssumeInit( -1 );
            i++;
            break;
        }

        /*---- get the info ptr for the register ----*/

        info = NULL;
        if ( tokenarray[i].token == T_REG ) {
            reg = tokenarray[i].tokval;
            j = GetRegNo( reg );
            flags = GetValueSp( reg );
            if ( flags & OP_SR ) {
                info = &SegAssumeTable[j];
                segtable = TRUE;
            } else if ( flags & OP_R ) {
                info = &StdAssumeTable[j];
                segtable = FALSE;
            }
        }
        if ( info == NULL ) {
            return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].string_ptr ) );
        }

        if( ( ModuleInfo.curr_cpu & P_CPU_MASK ) < GetCpuSp( reg ) ) {
            return( EmitError( INSTRUCTION_OR_REGISTER_NOT_ACCEPTED_IN_CURRENT_CPU_MODE ) );
        }

        i++; /* go past register */

        if( tokenarray[i].token != T_COLON ) {
            return( EmitError( COLON_EXPECTED ) );
        }
        i++;

        if( tokenarray[i].token == T_FINAL ) {
            return( EmitError( SYNTAX_ERROR ) );
        }

        /* check for ERROR and NOTHING */

        if( 0 == _stricmp( tokenarray[i].string_ptr, szError )) {
            if ( segtable ) {
                info->is_flat = FALSE;
                info->error = TRUE;
            } else
                info->error |= (( reg >= T_AH && reg <= T_BH ) ? RH_ERROR : ( flags & OP_R ));
            info->symbol = NULL;
            i++;
        } else if( 0 == _stricmp( tokenarray[i].string_ptr, szNothing )) {
            if ( segtable ) {
                info->is_flat = FALSE;
                info->error = FALSE;
            } else
                info->error &= ~(( reg >= T_AH && reg <= T_BH ) ? RH_ERROR : ( flags & OP_R ));
            info->symbol = NULL;
            i++;
        } else if ( segtable == FALSE ) {

            /* v2.05: changed to use new GetQualifiedType() function */
            ti.size = 0;
            ti.is_ptr = 0;
            ti.is_far = FALSE;
            ti.mem_type = MT_EMPTY;
            ti.ptr_memtype = MT_EMPTY;
            ti.symtype = NULL;
            ti.Ofssize = ModuleInfo.Ofssize;
            if ( GetQualifiedType( &i, tokenarray, &ti ) == ERROR )
                return( ERROR );

            /* v2.04: check size of argument! */
            size = OperandSize( flags, NULL );
            if ( ( ti.is_ptr == 0 && size != ti.size ) ||
                ( ti.is_ptr > 0 && size < CurrWordSize ) ) {
                return( EmitError( TYPE_IS_WRONG_SIZE_FOR_REGISTER ) );
            }
            info->error &= ~(( reg >= T_AH && reg <= T_BH ) ? RH_ERROR : ( flags & OP_R ));
            if ( stdsym[j] == NULL ) {
                stdsym[j] = CreateTypeSymbol( NULL, "", FALSE );
                stdsym[j]->typekind = TYPE_TYPEDEF;
            }

            stdsym[j]->total_size = ti.size;
            stdsym[j]->mem_type   = ti.mem_type;
            stdsym[j]->is_ptr     = ti.is_ptr;
            stdsym[j]->isfar      = ti.is_far;
            stdsym[j]->Ofssize    = ti.Ofssize;
            stdsym[j]->ptr_memtype = ti.ptr_memtype; /* added v2.05 rc13 */
            if ( ti.mem_type == MT_TYPE )
                stdsym[j]->type = ti.symtype;
            else
                stdsym[j]->target_type = ti.symtype;

            info->symbol = stdsym[j];

        } else { /* segment register */
            struct expr opnd;

            /* v2.08: read expression with standard evaluator */
            if( EvalOperand( &i, tokenarray, Token_Count, &opnd, 0 ) == ERROR )
                return( ERROR );
            switch ( opnd.kind ) {
            case EXPR_ADDR:
                if ( opnd.sym == NULL || opnd.indirect == TRUE || opnd.value ) {
                    return( EmitError( SEGMENT_GROUP_OR_SEGREG_EXPECTED ) );
                } else if ( opnd.sym->state == SYM_UNDEFINED ) {
                    /* ensure that directive is rerun in pass 2
                     * so an error msg can be emitted.
                     */
                    FStoreLine( 0 );
                    info->symbol = opnd.sym;
                } else if ( ( opnd.sym->state == SYM_SEG || opnd.sym->state == SYM_GRP ) && opnd.instr == EMPTY ) {
                    info->symbol = opnd.sym;
                } else if ( opnd.instr == T_SEG ) {
                    info->symbol = opnd.sym->segment;
                } else {
                    return( EmitError( SEGMENT_GROUP_OR_SEGREG_EXPECTED ) );
                }
                info->is_flat = ( info->symbol == &ModuleInfo.flat_grp->sym );
                break;
            case EXPR_REG:
                if ( GetValueSp( opnd.base_reg->tokval ) & OP_SR ) {
                    info->symbol = SegAssumeTable[ GetRegNo( opnd.base_reg->tokval ) ].symbol;
                    info->is_flat = SegAssumeTable[ GetRegNo( opnd.base_reg->tokval ) ].is_flat;
                    break;
                }
            default:
                return( EmitError( SEGMENT_GROUP_OR_SEGREG_EXPECTED ) );
            }
            info->error = FALSE;
        }

        /* comma expected */
        if( i < Token_Count && tokenarray[i].token != T_COMMA )
            break;
    }
    if ( i < Token_Count ) {
        return( EmitErr( SYNTAX_ERROR_EX, tokenarray[i].tokpos ) );
    }
    return( NOT_ERROR );
}

/* for a symbol, search segment register which holds segment
 * part of symbol's address in assume table.
 * - sym: segment of symbol for which to search segment register
 * - def: prefered default register (or ASSUME_NOTHING )
 * - search_grps: if TRUE, check groups as well
 *
 * for data items, Masm checks assumes in this order:
 *   DS, SS, ES, FS, GS, CS
 */

enum assume_segreg search_assume( const struct asym *sym,
                  enum assume_segreg def, bool search_grps )
/**********************************************************/
{
    struct asym *grp;

    if( sym == NULL )
        return( ASSUME_NOTHING );

    grp = GetGroup( sym );

    /* first check the default segment register */

    if( def != ASSUME_NOTHING ) {
        if( SegAssumeTable[def].symbol == sym )
            return( def );
        if( search_grps && grp ) {
            if( SegAssumeTable[def].is_flat && grp == &ModuleInfo.flat_grp->sym )
                return( def );
            if( SegAssumeTable[def].symbol == grp )
                return( def );
        }
    }

    /* now check all segment registers */

    for( def = 0; def < NUM_SEGREGS; def++ ) {
        if( SegAssumeTable[searchtab[def]].symbol == sym ) {
            return( searchtab[def] );
        }
    }

    /* now check the groups */
    if( search_grps && grp )
        for( def = 0; def < NUM_SEGREGS; def++ ) {
            if( SegAssumeTable[searchtab[def]].is_flat && grp == &ModuleInfo.flat_grp->sym )
                return( searchtab[def] );
            if( SegAssumeTable[searchtab[def]].symbol == grp ) {
                return( searchtab[def] );
            }
        }

    return( ASSUME_NOTHING );
}

/*
 called by the parser's seg_override() function if
 a segment register override has been detected.
 - override: segment register override (0,1,2,3,4,5)
*/

struct asym *GetOverrideAssume( enum assume_segreg override )
/***********************************************************/
{
    if( SegAssumeTable[override].is_flat ) {
        return( (struct asym *)ModuleInfo.flat_grp );
    }
    return( SegAssumeTable[override].symbol);

}

#if STACKBASESUPP
/* v2.17: get word size of a segment assume ( usually SS )
 */
enum segofssize GetOfssizeAssume( enum assume_segreg segno )
/**********************************************************/
{
    if( SegAssumeTable[segno].is_flat )
        return( ModuleInfo.Ofssize );

    if ( SegAssumeTable[segno].symbol ) {
        struct asym *sym = SegAssumeTable[segno].symbol;
        if ( sym->state == SYM_SEG )
            return( ((struct dsym *)sym)->e.seginfo->Ofssize );
        return( sym->Ofssize );
    }

    return( ModuleInfo.Ofssize );

}
#endif
/*
 * GetAssume():
 * called by check_assume() in parser.c
 * in:
 * - override: SegOverride
 * - sym: symbol in current memory operand
 * - def: default segment assume value
 * to be fixed: check if symbols with state==SYM_STACK are handled correctly.
 */

enum assume_segreg GetAssume( const struct asym *override, const struct asym *sym, enum assume_segreg def, struct asym * *passume )
/*********************************************************************************************************************************/
{
    enum assume_segreg  reg;

    /* v2.17: handle case if model isn't flat, but current segment is 64-bit ( flatgrp3.asm ) */
    //if( ( def != ASSUME_NOTHING ) && SegAssumeTable[def].is_flat ) {
    if( ( def != ASSUME_NOTHING ) && ( SegAssumeTable[def].is_flat
#if AMD64_SUPPORT
         || ModuleInfo.Ofssize == USE64
#endif
        ) ) {
        *passume = (struct asym *)ModuleInfo.flat_grp;
        return( def );
    }
    if( override != NULL ) {
        reg = search_assume( override, def, FALSE );
#if 1 /* v2.10: added */
    } else if ( sym->state == SYM_STACK ) {
        /* stack symbols don't have a segment part.
         * In case [R|E]BP is used as base, it doesn't matter.
         * However, if option -Zg is set, this isn't true.
         */
        reg = ASSUME_SS;
#endif
    } else {
        reg = search_assume( sym->segment, def, TRUE );
    }
    if( reg == ASSUME_NOTHING ) {
        if( sym && sym->state == SYM_EXTERNAL && sym->segment == NULL ) {
            reg = def;
        }
    }
    if( reg != ASSUME_NOTHING ) {
        *passume = SegAssumeTable[reg].symbol;
        return( reg );
    }
    *passume = NULL;
    return( ASSUME_NOTHING );
}

