i860.c   [plain text]


/* i860.c -- Assemble for the i860
   Copyright (C) 1989 Free Software Foundation, Inc.

This file is part of GAS, the GNU Assembler.

GAS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

GAS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GAS; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <mach-o/i860/reloc.h>

#include "i860-opcode.h"
#include "as.h"
#include "flonum.h"
#include "expr.h"
#include "hash.h"
#include "frags.h"
#include "fixes.h"
#include "read.h"
#include "md.h"
#include "symbols.h"
#include "messages.h"
#include "sections.h"

/*
 * These are the default cputype and cpusubtype for the i860 architecture.
 */
const cpu_type_t md_cputype = CPU_TYPE_I860;
cpu_subtype_t md_cpusubtype = CPU_SUBTYPE_I860_ALL;

/*
 * This is the byte sex for the i860 architecture.  The chip is running in
 * big endian mode so the assembler puts out the entire file (instructions
 * included) in big endian.  When the program is loaded in memory to be run
 * the program doing the loading byte swaps the fix width instructions.  If
 * this is not to be done by the loading program then BYTE_SWAP can be defined
 * in here that will put out the instructiona in little endian in the object
 * file.
 */
const enum byte_sex md_target_byte_sex = BIG_ENDIAN_BYTE_SEX;

static int i860_ip(
    char *str);
static void md_insn_to_chars(
    unsigned char *buf,
    int32_t val,
    int n);

const relax_typeS md_relax_table[] = { {0} };

/* handle of the OPCODE hash table */
static struct hash_control *op_hash = NULL;

static void s_dual(
    uintptr_t mode);
static void s_i860_align(
    uintptr_t value);
static void s_i860_org(
    uintptr_t value);

const pseudo_typeS md_pseudo_table[] = {
    { "float",	float_cons,	'f'	},
    { "int",	cons,		4	},
    { "align",	s_i860_align,	0	},	/* Alignment is in bytes */
    { "blkb",	s_space,	0	},	/* Reserve space, in bytes */
    { "dual",	s_dual,		1	},	/* Dual insn mode crock */
    { "enddual",s_dual,		0	},
    { "extern",	s_globl,	0	},	/* as860 equiv of .globl */
    { "ln",	s_line,		0	},	/* as860 equiv of .line */
    { "org",	s_i860_org,	0	},
#ifndef NeXT_MOD
    { "quad",	big_cons,	16	},	/* A quad is 16 bytes on 860 */
#endif /* NeXT_MOD */
    { "string",	stringer,	1	},	/* as860 equiv of .asciz */
    { NULL,     0,		0	},
};

static int dual_insn_mode = 0;

/* This array holds the chars that always start a comment.  If the
    pre-processor is disabled, these aren't very useful */
const char md_comment_chars[] = "|!";

/* This array holds the chars that only start a comment at the beginning of
   a line.  If the line seems to have the form '# 123 filename'
   .line and .file directives will appear in the pre-processed output */
/* Note that input_file.c hand checks for '#' at the beginning of the
   first line of the input file.  This is because the compiler outputs
   #NO_APP at the beginning of its output. */
/* Also note that a '/' followed by a '*' will always start a comment */
const char md_line_comment_chars[] = "#";

/* Chars that can be used to separate mant from exp in floating point nums */
const char md_EXP_CHARS[] = "eE";

/* Chars that mean this number is a floating point constant */
/* As in 0f12.456 */
/* or    0d1.2345e12 */
const char md_FLT_CHARS[] = "rRsSfFdDxXpP";

/* Also be aware that MAXIMUM_NUMBER_OF_CHARS_FOR_FLOAT may have to be
   changed in read.c .  Ideally it shouldn't have to know about it at all,
   but nothing is ideal around here.
 */
int size_reloc_info = sizeof(struct relocation_info);

static unsigned char octal[256];
#define isoctal(c)  octal[c]
static unsigned char toHex[256];

/* Local fatal error flag.  Used to bomb assembler in md_end after scanning input */
static int I860_errors;

static int insn_count;	/* Track insns assembled, as a word count */

struct i860_it {
    char    *error;
    uint32_t opcode;
    nlist_t *nlistp;
    expressionS exp;
    int pcrel;
    enum reloc_type_i860 reloc;
};
static struct i860_it the_insn;

#ifdef I860_DEBUG
static void print_insn(
    struct i860_it *insn);
#endif /* I860_DEBUG */

static int getExpression(
    char *str);
static char *expr_end;

/* Flags returned by i860_ip() */
#define INSERT_NOP	0x00000001

static
void
s_dual(
uintptr_t mode)
{
	dual_insn_mode = mode;
}

static
void
s_i860_align(
uintptr_t value)
{
    register unsigned int temp;
    register int32_t temp_fill;
    unsigned int i = 0;
    unsigned int bytes;
    char *toP, fill;

    bytes = temp = get_absolute_expression ();
#define MAX_ALIGNMENT (1 << 15)
    if ( temp > MAX_ALIGNMENT ) {
	as_warn("Alignment too large: %d. assumed.", temp = MAX_ALIGNMENT);
    }

    /*
     * For the i860, `.align (1<<n)' actually means `.align n'
     * so we have to convert it.
     */
    if (temp != 0) {
	for (i = 0; (temp & 1) == 0; temp >>= 1, ++i)
	    ;
    }
    if (temp != 1) {
	as_warn("Alignment not a power of 2");
    }
    temp = i;
    if (*input_line_pointer == ',') {
	input_line_pointer ++;
	temp_fill = get_absolute_expression ();
    } else {
	if ( frchain_now->frch_nsect == text_nsect )
    		temp_fill = OP_NOP;
	else
		temp_fill = 0;
    }
    if ( frchain_now->frch_nsect == text_nsect )	/* emit NOPs! */
    {	/* Grow the code frag as needed and dump nops into it. */
	if ( bytes & 3 )
		as_warn( "Instruction alignment must be a multiple of 4." );
	bytes &= ~3;
	/* This is really tacky, but works for a fixed width insn machine */
    	while ( bytes && ((insn_count * 4) % bytes) != 0 ) 
	{
		toP = frag_more(4);	/* Add an instruction */
		/* put out the opcode */
		md_insn_to_chars((unsigned char *)toP, temp_fill, 4);	/* Fill instruction */
		insn_count++;
	}
    	/* Clean up */
	demand_empty_rest_of_line();
	return;
    }
    /* Only make a frag if we HAVE to. . . */
    if (temp) {
	md_number_to_chars(&fill, temp_fill, 1);
	frag_align(temp, &fill, 1, 0);
    }
    /*
     * If this alignment is larger than any previous alignment then this
     * becomes the section's alignment.
     */
    if(frchain_now->frch_section.align < temp)
	frchain_now->frch_section.align = temp;
    demand_empty_rest_of_line();
    return;
}

static
void
s_i860_org(
uintptr_t value)
{
	register segT segment;
	expressionS exp;
	register int32_t temp_fill;
	register char *p;
	extern segT get_known_segmented_expression();

/*
 * Don't believe the documentation of BSD 4.2 AS.
 * There is no such thing as a sub-segment-relative origin.
 * Any absolute origin is given a warning, then assumed to be segment-relative.
 * Any segmented origin expression ("foo+42") had better be in the right
 * segment or the .org is ignored.
 *
 * BSD 4.2 AS warns if you try to .org backwards. We cannot because we
 * never know sub-segment sizes when we are reading code.
 * BSD will crash trying to emit -ve numbers of filler bytes in certain
 * .orgs. We don't crash, but see as-write for that code.
 */
	segment = get_known_segmented_expression(& exp);
	if ( *input_line_pointer == ',' ) {
		input_line_pointer ++;
		temp_fill = get_absolute_expression ();
	} else
		temp_fill = 0;

	if((segment != SEG_SECT ||
	    exp.X_add_symbol->sy_other != frchain_now->frch_nsect) &&
	    segment != SEG_ABSOLUTE)
	    as_warn("Illegal expression. current section assumed.");

	if ( exp.X_add_symbol != NULL )
		as_warn("Symbol relative .org may corrupt alignment.");
	else if ( exp.X_add_number & 3 )
	{
	    exp.X_add_number &= ~3;
	    as_warn(".org not on instruction boundry. Adjusted to \".org "
		    "%lld\"", exp.X_add_number);
	}
	if ( exp.X_add_symbol == NULL )
		insn_count = exp.X_add_number >> 2;
	p = frag_var (rs_org, 1, 1, (relax_substateT)0, exp . X_add_symbol,
exp . X_add_number, (char *)0);
	* p = temp_fill;

	demand_empty_rest_of_line();
}


/*
 * This function is called once, at assembler startup time.  This should
 * set up all the tables, etc that the MD part of the assembler needs
 */
void
md_begin(
void)
{
    const char *retval = NULL;
    uint32_t i;
    int j = 0;

    insn_count = 0;
    if ((op_hash = hash_new()) == NULL)
	as_fatal("Virtual memory exhausted");

    for (i = 0; i < NUMOPCODES; ++i) {
	if (~i860_opcodes[i].mask & i860_opcodes[i].match) {
	    printf("bad opcode - `%s %s'\n",
		i860_opcodes[i].name, i860_opcodes[i].args);
	    ++j;
	}
    }

    if (j)
	exit(1);

    for (i = 0; i < NUMOPCODES; ++i) {
      retval = hash_insert(op_hash, (char *)i860_opcodes[i].name,
			   (char *)&i860_opcodes[i]);
      if(retval && *retval) {
	  as_fatal("Internal Error:  Can't hash %s: %s",
	    i860_opcodes[i].name, retval);
      }
      while (!i860_opcodes[i].last)
	  ++i;
    }
    for (i = '0'; i < '8'; ++i)
	octal[i] = 1;
    for (i = '0'; i <= '9'; ++i)
	toHex[i] = i - '0';
    for (i = 'a'; i <= 'f'; ++i)
	toHex[i] = i + 10 - 'a';
    for (i = 'A'; i <= 'F'; ++i)
	toHex[i] = i + 10 - 'A';

    I860_errors = 0;
    return;
}

void
md_end(
void)
{
    if ( I860_errors )
    {
    	fprintf( stderr, "%d fatal %s encountered during assembly.\n", I860_errors,
		(I860_errors == 1 ? "error" : "errors") );
    	exit( 42 );	/* Fatal errors seen during assembly */
    }

    return;
}

void
md_assemble(
char *str)
{
    char *toP;
    int flags;

    assert(str);
    flags = i860_ip(str);
    if ( flags & INSERT_NOP )
    {
        toP = frag_more(4);
        /* put out the opcode */
        md_insn_to_chars((unsigned char *)toP, OP_NOP, 4);
	++insn_count;
    }
    toP = frag_more(4);
    /* put out the opcode */
    md_insn_to_chars((unsigned char *)toP, the_insn.opcode, 4);
    ++insn_count;

    /* put out the symbol-dependent stuff */
    if (the_insn.reloc != NO_RELOC) {
	fix_new(
	    frag_now,                           /* which frag */
	    (toP - frag_now->fr_literal), /* where */
	    4,                                  /* size */
	    the_insn.exp.X_add_symbol,
	    the_insn.exp.X_subtract_symbol,
	    the_insn.exp.X_add_number,
	    the_insn.pcrel, 0,
	    the_insn.reloc
	);
    }
}

static
int
i860_ip(
char *str)
{
    char *s;
    char *op;
    const char *args;
    char c;
    struct i860_opcode *insn;
    char *argsStart;
    char *s1;
    uint32_t   opcode;
    unsigned int mask;
    int this_insn_is_dual = 0;
    int adjustment;
    int	align_mask;
    int match = FALSE;
    int comma = 0;
    int flags = 0;
    static int expect_int_insn;	/* Tracking for fp/int insns in dual mode. */

    /* Advance s to end of opcode */
    for (s = str; islower(*s) || *s == '.' || isdigit(*s); ++s)
	;
    switch (*s) {

    case '\0':
	break;

    case ',':
	comma = 1;

	/*FALLTHROUGH*/

    case ' ':
    case '\t':
	*s++ = '\0';
	break;

    default:
	    as_warn("Unknown opcode: `%s'", str);
	    exit(1);
    }
    /* Code to sniff for 'd.' prefix here and flag for dual insn mode */
    op = str;
    if ( *op == 'd' && *(op + 1) == '.' )
    {
    	op += 2;
	this_insn_is_dual = 1;
    }
        
    if ((insn = (struct i860_opcode *) hash_find(op_hash, op)) == NULL) {
	as_warn("Unknown instruction or format: `%s'.", str);
	memset(&the_insn, '\0', sizeof(the_insn));	/* Patch in no-op to hold alignment */
	the_insn.reloc = NO_RELOC;
	the_insn.opcode = OP_NOP;
	++I860_errors;				/* Flag as fatal error */
	return flags;
    }
    if (comma) {
	*--s = ',';
    }
    argsStart = s;
    for (;;) {
	opcode = insn->match;
	memset(&the_insn, '\0', sizeof(the_insn));
	the_insn.reloc = NO_RELOC;

	/*
	 * Build the opcode, checking as we go to make
	 * sure that the operands match
	 */
	for (args = insn->args; ; ++args) {
	    align_mask = 0;
	    switch (*args) {

	    case '\0':  /* end of args */
		if (*s == '\0') {
		    match = TRUE;
		}
		break;

	    case '+':
	    case '(':   /* these must match exactly */
	    case ')':
	    case ',':
	    case ' ':
		if (*s++ == *args)
		    continue;
		break;
		
	    case 'C':   /* Control register */
		if (strncmp(s, "fir", 3) == 0) {
		    s += 3;
		    SET_RS2(opcode, 0);
		    continue;
		}
		if (strncmp(s, "psr", 3) == 0) {
		    s += 3;
		    SET_RS2(opcode, 1);
		    continue;
		}
		if (strncmp(s, "dirbase", 7) == 0) {
		    s += 7;
		    SET_RS2(opcode, 2);
		    continue;
		}
		if (strncmp(s, "db", 2) == 0) {
		    s += 2;
		    SET_RS2(opcode, 3);
		    continue;
		}
		if (strncmp(s, "fsr", 3) == 0) {
		    s += 3;
		    SET_RS2(opcode, 4);
		    continue;
		}
		if (strncmp(s, "epsr", 4) == 0) {
		    s += 4;
		    SET_RS2(opcode, 5);
		    continue;
		}
		break;

	    case '1':	/* next operand must be a register */
	    case '2':
	    case 'd':
		{
		    switch (c = *s++) {

		    case 'f':   /* frame pointer */
		        if (*s++ == 'p') {
			    mask = 3;	/* register fp is alias for r3 */
			    break;
			}
			goto error;

		    case 's':   /* global register */
			if (*s++ == 'p') {
			    mask = 2;	/* register sp is alias for r2 */
			    break;
			}
			goto error;

		    case 'r': /* any register */
		        if (!isdigit(c = *s++)) {
			    goto error;
			}
			if (isdigit(*s)) {
			    if ((c = 10 * (c - '0') + (*s++ - '0')) >= 32) {
				goto error;
			    }
			} else {
			    c -= '0';
			}
			mask= c;
			break;

		    default:
			goto error;
		    }
		    /*
		     * Got the register, now figure out where
		     * it goes in the opcode.
		     */
		    switch (*args) {

		    case '1':
		    	SET_RS1(opcode, mask);
			continue;

		    case '2':
		    	SET_RS2(opcode, mask);
			continue;

		    case 'd':
		    	SET_RD(opcode, mask);
			continue;
		    }
		}
		break;

	    case 'e':    /* next operand is a floating point register */
	    case 'f':
	    case 'g':
	    case 'E':
	    case 'F':
	    case 'G':
	    case 'H':
	        if (*s++ == 'f' && isdigit(*s)) {
		    mask = *s++;
		    if (isdigit(*s)) {
			mask = 10 * (mask - '0') + (*s++ - '0');
			if (mask >= 32) {
			    break;
			}
		    } else {
			mask -= '0';
		    }
		    if ( (*args == 'E' || *args == 'F' || *args == 'G') && (mask & 1) )
		    {
		    	as_warn( "f%d: Even register required.  Adjusted to f%d.",
				mask, mask & 0x1E );
			mask &= 0x1E;
		    }
		    else if ( *args == 'H' && (mask & 3) )
		    {
		    	as_warn( "f%d: Quad register required.  Adjusted to f%d.",
				mask, mask & 0x1C );
			mask &= 0x1C;
		    }
		    switch (*args) {

		    case 'e':
		    case 'E':
		    	SET_RS1(opcode, mask);
			continue;

		    case 'f':
		    case 'F':
		    	SET_RS2(opcode, mask);
			continue;

		    case 'g':
		    case 'G':
		    case 'H':
		    	SET_RD(opcode, mask);
			continue;
		    }
		}
		break;
	
	    case 'B':	/* 5 bit immediate unsigned constant */	
	  	(void)getExpression(s);
		s = expr_end;
		if ( the_insn.exp.X_seg != SEG_ABSOLUTE )
		{
			as_warn( "Constant expression expected" );
			++I860_errors;
		}
		if ( the_insn.exp.X_add_number < 0 || the_insn.exp.X_add_number > 31 )
		    as_warn( "Constant must be between 0 and 31. Modulo 32 applied." );
		SET_RS1(opcode, the_insn.exp.X_add_number); /* Takes const modulo 32 */
		continue;
		
	    case 'D':	/* immediate unsigned constant used in shift opcodes */	
	  	(void)getExpression(s);
		s = expr_end;
		if ( the_insn.exp.X_seg != SEG_ABSOLUTE )
		{
			as_warn( "Constant expression expected" );
			++I860_errors;
		}
		if ( the_insn.exp.X_add_number < 0 || the_insn.exp.X_add_number > 31 )
		    as_warn( "Constant must be between 0 and 31. Modulo 32 applied." );
		opcode |= (the_insn.exp.X_add_number & 0x1F);
		continue;

	    case 'i':       /* low 16 bits, byte aligned */
	        the_insn.reloc = I860_RELOC_LOW0;
		goto immediate;
		
	    case 'I':       /* high 16 bits */
	        the_insn.reloc = I860_RELOC_HIGH;
		goto immediate;

	    case 'j':       /* low 16 bits, short aligned */
	        the_insn.reloc = I860_RELOC_LOW1;
		align_mask = 1;
		goto immediate;

	    case 'k':       /* low 16 bits, int aligned */
	        the_insn.reloc = I860_RELOC_LOW2;
		align_mask = 3;
		goto immediate;

	    case 'l':       /* low 16 bits, double aligned */
	        the_insn.reloc = I860_RELOC_LOW3;
		align_mask = 7;
		goto immediate;

	    case 'm':       /* low 16 bits, quad aligned */
	        the_insn.reloc = I860_RELOC_LOW4;
		align_mask = 15;
		goto immediate;

	    case 'n':       /* low 16 bits, byte aligned, split field */
	        the_insn.reloc = I860_RELOC_SPLIT0;
		goto immediate;

	    case 'o':       /* low 16 bits, short aligned, split field */
	        the_insn.reloc = I860_RELOC_SPLIT1;
		align_mask = 1;
		goto immediate;

	    case 'p':       /* low 16 bits, int aligned, split field */
	        the_insn.reloc = I860_RELOC_SPLIT2;
		align_mask = 3;
		goto immediate;

	    case 'J':       /* High 16 bits, requiring adjustment */
	        the_insn.reloc = I860_RELOC_HIGHADJ;
		goto immediate;


	    case 'K':   /* 26 bit PC relative immediate */
	        the_insn.reloc = I860_RELOC_BRADDR;
		the_insn.pcrel = 1;
		goto immediate;

	    case 'L':   /* 16 bit PC relative split format immediate */
	        the_insn.reloc = I860_RELOC_SPLIT0;
		the_insn.pcrel = 1;
		goto immediate;
		/*FALLTHROUGH*/

	    immediate:
		if(*s==' ')
		  s++;
		adjustment = 0;
		if ( *s == 'h' && *(s + 1) == '%' )
		{
			adjustment = I860_RELOC_HIGH;
			if ( the_insn.reloc == I860_RELOC_LOW0 && the_insn.pcrel == 0 )
			{
				the_insn.reloc = I860_RELOC_HIGH;
			}
			else
				as_warn("Improper use of h%%.");
			s += 2;
		}
		else if ( *s == 'h' && *(s + 1) == 'a' && *(s + 2) == '%' )
		{
			adjustment = I860_RELOC_HIGHADJ;
			if ( the_insn.reloc == I860_RELOC_LOW0 && the_insn.pcrel == 0 )
			{
				the_insn.reloc = I860_RELOC_HIGHADJ;
			}
			else
				as_warn("Improper use of ha%%.");

			s += 3;
		}
		else if ( *s == 'l' &&  *(s + 1) == '%' )
		{	/* the_insn.reloc is correct as is. */
			adjustment = I860_RELOC_LOW0;
			s += 2;
		}
		/* Note that if the getExpression() fails, we will still have
		   created U entries in the symbol table for the 'symbols'
		   in the input string.  Try not to create U symbols for
		   registers, etc. */
		   
		/* This stuff checks to see if the expression ends
		   in '(', as in ld.l foo(r20). If it does, it
		   removes the '(' from the expression, and 
		   re-sets 's' to point to the right place */

		for(s1=s;*s1 && *s1!=','&& *s1!=')';s1++)
			;

		if( s1 != s && *s1 == '(' && s1[1] == 'r' && isdigit(s1[2])
		    && (s1[3]==')' || (isdigit(s1[3]) && s1[4] == ')')) ) {
				*s1='\0';
				(void)getExpression(s);
				*s1='(';
				s=s1;
		}
		else
		{
			(void)getExpression(s);
			s = expr_end;
		}
		/*
		 * If there is an adjustment, we assume the user knows what
		 * they are doing.  If no adjustment, we very carefully range 
		 * check for both signed and unsigned operations, to avoid 
		 * unpleasant suprises.  The checks are skipped for branch and
		 * call instructions, matching the behavior of the Intel assembler.
		 */
		if ( ! adjustment && *op != 'b' && *op != 'c' )
		{
		    if ( the_insn.exp.X_seg != SEG_ABSOLUTE )
		    {
			as_warn(
			  "Non-absolute expression requires h%%, l%%, or ha%% prefix."
			);
		    }
		    else
		    {
		    	if ( IS_LOGOP(opcode) )
			{
			    if ( ((unsigned)the_insn.exp.X_add_number) > 0xFFFF )
			    	as_warn("%lld is too big for 16 bit unsigned value!",
					the_insn.exp.X_add_number);
			}
			else
			{
			    if ( ((int)the_insn.exp.X_add_number) > 32767 ||
			    	 ((int)the_insn.exp.X_add_number) < -32768 )
				as_warn("%lld is out of range for 16 bit signed value!",
					the_insn.exp.X_add_number);
					
			    if ((align_mask & the_insn.exp.X_add_number) != 0)
			    	as_warn("Const offset 0x%x incorrectly aligned.",
					(unsigned int)the_insn.exp.X_add_number);
			}
		    }
		}
		continue;
		
	    default:
		abort();
	    }
	    break;
	}
	error:
	if (match == FALSE) {
	    /* args don't match */
	    if (!insn->last) {
		++insn;
		s = argsStart;
		continue;
	    } else {
		as_warn("Illegal operands (%s %s).", str, argsStart);
		++I860_errors;
		return flags;
	    }
	}
	break;
    }
    /* If the last insn was dual, check and make sure that this insn is integer insn */
    if ( expect_int_insn && (dual_insn_mode || this_insn_is_dual) )
    {
    	    if ( (opcode & OP_PREFIX_MASK) == PREFIX_FPU || opcode == OP_FNOP )
	    {
		as_warn( "Core half of prev dual insn pair missing." );
	    }
	    expect_int_insn = 0;
    }
    /* Check the insn format and fold in the dual mode bit if appropriate. */
    if ( dual_insn_mode || this_insn_is_dual )
    {
	    if ( (opcode & OP_PREFIX_MASK) == PREFIX_FPU || opcode == OP_FNOP )
	    {
	    	    if ( insn_count & 1 )	/* Odd insn, not on 64 bit bound! */
		    {
			as_warn( "Dual FP insn on odd addr." );
		    }
		    opcode |= DUAL_INSN_MODE_BIT;
		    expect_int_insn = 1;
	    }
	    else if ( this_insn_is_dual ) /* d. prefix on a non-FPU insn error */
	    {
		    as_warn("d. prefix not allowed for `%s'. (Ignored!)", op);
	    } 
    }
    else
    	    expect_int_insn = 0;	/* Single insn mode. */
    /* Check for correct alignment of const in branch to label + offset */
    if (    (the_insn.reloc == I860_RELOC_BRADDR
    	     || (the_insn.pcrel && the_insn.reloc == I860_RELOC_SPLIT0)
	    ) && (the_insn.exp.X_add_number & 3) )
    		as_warn( "Branch offset is not aligned to instruction boundry!" );
    the_insn.opcode = opcode;
    return flags;
}

static
int
getExpression(
char *str)
{
    char *save_in;
    segT seg;

    save_in = input_line_pointer;
    input_line_pointer = str;
    switch (seg = expression(&the_insn.exp)) {

    case SEG_ABSOLUTE:
    case SEG_SECT:
    case SEG_DIFFSECT:
    case SEG_UNKNOWN:
    case SEG_BIG:
    case SEG_NONE:
	break;

    default:
	the_insn.error = "bad segment";
	expr_end = input_line_pointer;
	input_line_pointer=save_in;
	return 1;
    }
    expr_end = input_line_pointer;
    input_line_pointer = save_in;
    return 0;
}


#define MAX_LITTLENUMS 6

/*
    This is identical to the md_atof in m68k.c.  I think this is right,
    but I'm not sure.

   Turn a string in input_line_pointer into a floating point constant of type
   type, and store the appropriate bytes in *litP.  The number of LITTLENUMS
   emitted is stored in *sizeP .  An error message is returned, or NULL on OK.
 */
char *
md_atof(
int type,
char *litP,
int *sizeP)
{
    int	prec;
    LITTLENUM_TYPE words[MAX_LITTLENUMS];
    LITTLENUM_TYPE *wordP;
    char	*t;
    char	*atof_ieee();

    switch(type) {

    case 'f':
    case 'F':
    case 's':
    case 'S':
	prec = 2;
	break;

    case 'd':
    case 'D':
    case 'r':
    case 'R':
	prec = 4;
	break;
    /* The following two formats get reduced to doubles. */
    case 'x':
    case 'X':
    	type = 'd';
	prec = 4;
	break;

    case 'p':
    case 'P':
    	type = 'd';
	prec = 4;
	break;

    default:
	*sizeP=0;
	return "Bad call to MD_ATOF()";
    }
    t=atof_ieee(input_line_pointer,type,words);
    if(t)
	input_line_pointer=t;
    *sizeP=prec * sizeof(LITTLENUM_TYPE);
    for(wordP=words;prec--;) {
	md_number_to_chars(litP,(int32_t)(*wordP++),sizeof(LITTLENUM_TYPE));
	litP+=sizeof(LITTLENUM_TYPE);
    }
    return "";	/* Someone should teach Dean about null pointers */
}

/*
 * Write out big-endian.  Valid for data only in our I860 implementation.
 */
void
md_number_to_chars(
char *buf,
signed_expr_t val,
int n)
{

    switch(n) {

    case 4:
	*buf++ = val >> 24;
	*buf++ = val >> 16;
    case 2:
	*buf++ = val >> 8;
    case 1:
	*buf = val;
	break;

    default:
	abort();
    }
    return;
}

#ifdef BYTE_SWAP
/*
 * Write out little-endian.  Valid for instructions only in
 * our i860 implementation.
 */
static
void
md_insn_to_chars(
unsigned char *buf,
int32_t val,
int n)
{

    switch(n) {

    case 4:
	*buf++ = val;
	*buf++ = val >> 8;
	*buf++ = val >> 16;
	*buf++ = val >> 24;
	break;
    case 2:
	*buf++ = val;
	*buf++ = val >> 8;
	break;
    case 1:
	*buf = val;
	break;

    default:
	abort();
    }
    return;
}
#else /* !defined(BYTE_SWAP) */

static
void
md_insn_to_chars(
unsigned char *buf,
int32_t val,
int n)
{
	md_number_to_chars((char *)buf,val,n);
}
#endif /* BYTE_SWAP */

void
md_number_to_imm(
unsigned char *buf,
signed_expr_t val,
int n,
fixS *fixP,
int nsect)
{
    uint32_t opcode;

    if ( nsect == (int)text_nsect && (n % 4) != 0 )
    	as_warn("Immediate write of non-aligned data into text segment." );
	
    if (nsect != (int)text_nsect ||
	fixP->fx_r_type == NO_RELOC ||
	fixP->fx_r_type == I860_RELOC_VANILLA)
    {
	switch (n) {	/* Write out the data big-endian style. */
	case 1:
		*buf = val;
		break;
	case 2:
		*buf++ = (val>>8);
		*buf = val;
		break;
	case 4:
		*buf++ = (val>>24);
		*buf++ = (val>>16);
		*buf++ = (val>>8);
		*buf = val;
		break;
	default:
		abort();
	}
	return;
    }

    assert(n == 4);	/* Better be an instruction with relocation data.... */
    assert(fixP->fx_r_type < NO_RELOC && fixP->fx_r_type > I860_RELOC_VANILLA);
    /*
     * Here is where we do initial bit fiddling to load immediate 
     * values into the i860 bit fields.
     */
#ifdef BYTE_SWAP     
    /* Note that all of these insns are ultimately little-endian */
    /* Get the opcode from the buffer.  Less efficient, but more coherent... */
    opcode = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
#else
    opcode = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
#endif  
    /* Apply the relocation value 'val' */
    switch (fixP->fx_r_type) { 
    	case I860_RELOC_PAIR:
		as_warn("questionable relocation type I860_RELOC_PAIR");
		break;
    	case I860_RELOC_HIGH:
		opcode &= ~0xFFFF;
		opcode |= ((val >> 16) & 0xFFFF);
		break;
    	case I860_RELOC_LOW0:
		opcode &= ~0xFFFF;
		opcode |= (val & 0xFFFF);
		break;
    	case I860_RELOC_LOW1:
		opcode &= 0xFFFF0001;
		opcode |= (val & 0xFFFE);	/* Bit 0 is an insn bit! */
		break;
    	case I860_RELOC_LOW2:
		opcode &= 0xFFFF0003;
		opcode |= (val & 0xFFFC);	/* Bits 0 and 1 are insn bits! */
		break;
    	case I860_RELOC_LOW3:
		opcode &= 0xFFFF0007;
		opcode |= (val & 0xFFF8);	/* Bits 0 thru 2 are insn bits! */
		break;
    	case I860_RELOC_LOW4:
		opcode &= 0xFFFF000F;
		opcode |= (val & 0xFFF0);	/* Bits 0 thru 3 are insn bits! */
		break;
    	case I860_RELOC_SPLIT0:
		opcode &= 0xFFE0F800;
		if ( fixP->fx_pcrel ) 		/* A 16 bit branch relative insn? */
			val >>= 2;		/* Convert to word address */
		opcode |= ((val & 0xF800) << 5) | (val & 0x7FF);
		break;
    	case I860_RELOC_SPLIT1:
		opcode &= 0xFFE0F801;		/* Again, bit 0 is an insn bit! */
		opcode |= ((val & 0xF800) << 5) | (val & 0x7FE);
		break;
    	case I860_RELOC_SPLIT2:
		opcode &= 0xFFE0F803;		/* Bits 0 and 1 are insn bits! */
		opcode |= ((val & 0xF800) << 5) | (val & 0x7FC);
		break;
    	case I860_RELOC_HIGHADJ:			/* Adjusted variant */
		opcode &= ~0xFFFF;
		/* If the low half would be negative, compensate by adding 1 to 
		 * high half.
		 */
		if ( (val & 0x8000) != 0 )
			val = (val >> 16) + 1;
		else
			val = (val >> 16);
		opcode |= (val & 0xFFFF);
		break;
    	case I860_RELOC_BRADDR:
		if ( fixP->fx_pcrel ) 		/* A 26 bit branch relative insn? */
			val >>= 2;		/* Convert to word address */
		opcode &= 0xFC000000;
		opcode |= (val & 0x03FFFFFF);
		break;
	
	default:
		as_warn("bad relocation type: 0x%02x", fixP->fx_r_type);
		break;
	}
#ifdef BYTE_SWAP
	buf[0] = opcode;
	buf[1] = opcode >> 8;
	buf[2] = opcode >> 16;
	buf[3] = opcode >> 24;
#else
	buf[3] = opcode;
	buf[2] = opcode >> 8;
	buf[1] = opcode >> 16;
	buf[0] = opcode >> 24;
#endif
	return;
}

/* should never be called for i860 */
void
md_convert_frag(
fragS *fragP)
{
    fprintf(stderr, "i860_convert_frag\n");
    abort();
}

/* should never be called for i860 */
int
md_estimate_size_before_relax(
fragS *fragP,
int nsect)
{
    fprintf(stderr, "i860_estimate_size_before_relax\n");
    abort();
    return 0;
}

#ifdef I860_DEBUG
/* for debugging only */
static void
print_insn(
struct i860_it *insn)
{
    char *Reloc[] = {
    "RELOC_8",
    "RELOC_16",
    "RELOC_32",
    "RELOC_DISP8",
    "RELOC_DISP16",
    "RELOC_DISP32",
    "RELOC_WDISP30",
    "RELOC_WDISP22",
    "RELOC_HI22",
    "RELOC_22",
    "RELOC_13",
    "RELOC_LO10",
    "RELOC_SFA_BASE",
    "RELOC_SFA_OFF13",
    "RELOC_BASE10",
    "RELOC_BASE13",
    "RELOC_BASE22",
    "RELOC_PC10",
    "RELOC_PC22",
    "RELOC_JMP_TBL",
    "RELOC_SEGOFF16",
    "RELOC_GLOB_DAT",
    "RELOC_JMP_SLOT",
    "RELOC_RELATIVE",
    "NO_RELOC"
    };

    if (insn->error) {
	fprintf(stderr, "ERROR: %s\n");
    }
    fprintf(stderr, "opcode=0x%08x\n", insn->opcode);
    fprintf(stderr, "reloc = %s\n", Reloc[insn->reloc]);
    fprintf(stderr, "exp =  {\n");
    fprintf(stderr, "\t\tX_add_symbol = %s\n",
	insn->exp.X_add_symbol ?
	(insn->exp.X_add_symbol->sy_name ? 
	insn->exp.X_add_symbol->sy_name : "???") : "0");
    fprintf(stderr, "\t\tX_sub_symbol = %s\n",
	insn->exp.X_subtract_symbol ?
	    (insn->exp.X_subtract_symbol->sy_name ? 
	        insn->exp.X_subtract_symbol->sy_name : "???") : "0");
    fprintf(stderr, "\t\tX_add_number = %d\n",
	insn->exp.X_add_number);
    fprintf(stderr, "}\n");
    return;
}
#endif /* I860_DEBUG */

int
md_parse_option(
char **argP,
int *cntP,
char ***vecP)
{
    return 1;
}