binutils-gdb/gas/config/tc-z8k.c
Jan Beulich 641cf0e2c0 gas: constify md_{short,long}opts and md_longopts_size
First of all make the declarations globally visible, such that producer
and consumer actually share them.

For the latter two simply add const (as PPC already had it,), while for
the former achieve the effect by converting to an array: There's no need
for the extra level of indirection.
2024-10-29 08:08:02 +01:00

1584 lines
36 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* tc-z8k.c -- Assemble code for the Zilog Z800n
Copyright (C) 1992-2024 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 3, 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, 51 Franklin Street - Fifth Floor, Boston, MA
02110-1301, USA. */
/* Written By Steve Chamberlain <sac@cygnus.com>. */
#include "as.h"
#include "safe-ctype.h"
#define DEFINE_TABLE
#include "opcodes/z8k-opc.h"
const char comment_chars[] = "!";
const char line_comment_chars[] = "#";
const char line_separator_chars[] = ";";
extern int machine;
extern int coff_flags;
int segmented_mode;
/* This is non-zero if target was set from the command line.
If non-zero, 1 means Z8002 (non-segmented), 2 means Z8001 (segmented). */
static int z8k_target_from_cmdline;
static void
s_segm (int segm)
{
if (segm)
{
segmented_mode = 1;
bfd_set_arch_mach (stdoutput, TARGET_ARCH, bfd_mach_z8001);
}
else
{
segmented_mode = 0;
bfd_set_arch_mach (stdoutput, TARGET_ARCH, bfd_mach_z8002);
}
}
static void
even (int ignore ATTRIBUTE_UNUSED)
{
frag_align (1, 0, 0);
record_alignment (now_seg, 1);
}
static int
tohex (int c)
{
if (ISDIGIT (c))
return c - '0';
if (ISLOWER (c))
return c - 'a' + 10;
return c - 'A' + 10;
}
static void
sval (int ignore ATTRIBUTE_UNUSED)
{
SKIP_WHITESPACE ();
if (*input_line_pointer == '\'')
{
int c;
input_line_pointer++;
c = *input_line_pointer++;
while (c != '\'')
{
if (c == '%')
{
c = (tohex (input_line_pointer[0]) << 4)
| tohex (input_line_pointer[1]);
input_line_pointer += 2;
}
FRAG_APPEND_1_CHAR (c);
c = *input_line_pointer++;
}
demand_empty_rest_of_line ();
}
}
/* This table describes all the machine specific pseudo-ops the assembler
has to support. The fields are:
pseudo-op name without dot
function to call to execute this pseudo-op
Integer arg to pass to the function
*/
const pseudo_typeS md_pseudo_table[] = {
{"int" , cons , 2},
{"data.b" , cons , 1},
{"data.w" , cons , 2},
{"data.l" , cons , 4},
{"form" , listing_psize , 0},
{"heading", listing_title , 0},
{"import" , s_ignore , 0},
{"page" , listing_eject , 0},
{"program", s_ignore , 0},
{"z8001" , s_segm , 1},
{"z8002" , s_segm , 0},
{"segm" , s_segm , 1},
{"unsegm" , s_segm , 0},
{"unseg" , s_segm , 0},
{"name" , s_file , 0},
{"global" , s_globl , 0},
{"wval" , cons , 2},
{"lval" , cons , 4},
{"bval" , cons , 1},
{"sval" , sval , 0},
{"rsect" , obj_coff_section, 0},
{"sect" , obj_coff_section, 0},
{"block" , s_space , 0},
{"even" , even , 0},
{0 , 0 , 0}
};
const char EXP_CHARS[] = "eE";
/* Chars that mean this number is a floating point constant.
As in 0f12.456
or 0d1.2345e12 */
const char FLT_CHARS[] = "rRsSfFdDxXpP";
/* Opcode mnemonics. */
static htab_t opcode_hash_control;
void
md_begin (void)
{
const opcode_entry_type *opcode;
unsigned int idx = -1u;
opcode_hash_control = str_htab_create ();
for (opcode = z8k_table; opcode->name; opcode++)
{
/* Only enter unique codes into the table. */
if (idx != opcode->idx)
str_hash_insert (opcode_hash_control, opcode->name, opcode, 0);
idx = opcode->idx;
}
/* Default to z8002. */
s_segm (z8k_target_from_cmdline ? z8k_target_from_cmdline - 1 : 0);
/* Insert the pseudo ops, too. */
for (idx = 0; md_pseudo_table[idx].poc_name; idx++)
{
opcode_entry_type *fake_opcode;
fake_opcode = XNEW (opcode_entry_type);
fake_opcode->name = md_pseudo_table[idx].poc_name;
fake_opcode->func = (void *) (md_pseudo_table + idx);
fake_opcode->opcode = 250;
str_hash_insert (opcode_hash_control, fake_opcode->name, fake_opcode, 0);
}
}
typedef struct z8k_op {
/* CLASS_REG_xxx. */
int regsize;
/* 0 .. 15. */
unsigned int reg;
int mode;
/* Any other register associated with the mode. */
unsigned int x_reg;
/* Any expression. */
expressionS exp;
} op_type;
static expressionS *da_operand;
static expressionS *imm_operand;
static int reg[16];
static int the_cc;
static int the_ctrl;
static int the_flags;
static int the_interrupt;
/* Determine register number. src points to the ascii number
(after "rl", "rh", "r", "rr", or "rq"). If a character
outside the set of {0,',',')','('} follows the number,
return NULL to indicate that it's not a valid register
number. */
static char *
whatreg (unsigned int *preg, char *src)
{
unsigned int new_reg;
/* src[0] is already known to be a digit. */
if (ISDIGIT (src[1]))
{
new_reg = (src[0] - '0') * 10 + src[1] - '0';
src += 2;
}
else
{
new_reg = (src[0] - '0');
src += 1;
}
if (src[0] != 0 && src[0] != ',' && src[0] != '(' && src[0] != ')')
return NULL;
*preg = new_reg;
return src;
}
/* Parse operands
rh0-rh7, rl0-rl7
r0-r15
rr0-rr14
rq0--rq12
WREG r0,r1,r2,r3,r4,r5,r6,r7,fp,sp
r0l,r0h,..r7l,r7h
@WREG
@WREG+
@-WREG
#const
*/
/* Try to parse a reg name. Return a pointer to the first character
in SRC after the reg name. */
static char *
parse_reg (char *src, int *mode, unsigned int *preg)
{
char *res = NULL;
char regno;
/* Check for stack pointer "sp" alias. */
if ((src[0] == 's' || src[0] == 'S')
&& (src[1] == 'p' || src[1] == 'P')
&& (src[2] == 0 || src[2] == ','))
{
if (segmented_mode)
{
*mode = CLASS_REG_LONG;
*preg = 14;
}
else
{
*mode = CLASS_REG_WORD;
*preg = 15;
}
return src + 2;
}
if (src[0] == 'r' || src[0] == 'R')
{
if (src[1] == 'r' || src[1] == 'R')
{
if (src[2] < '0' || src[2] > '9')
return NULL; /* Assume no register name but a label starting with 'rr'. */
*mode = CLASS_REG_LONG;
res = whatreg (preg, src + 2);
if (res == NULL)
return NULL; /* Not a valid register name. */
regno = *preg;
if (regno > 14)
as_bad (_("register rr%d out of range"), regno);
if (regno & 1)
as_bad (_("register rr%d does not exist"), regno);
}
else if (src[1] == 'h' || src[1] == 'H')
{
if (src[2] < '0' || src[2] > '9')
return NULL; /* Assume no register name but a label starting with 'rh'. */
*mode = CLASS_REG_BYTE;
res = whatreg (preg, src + 2);
if (res == NULL)
return NULL; /* Not a valid register name. */
regno = *preg;
if (regno > 7)
as_bad (_("register rh%d out of range"), regno);
}
else if (src[1] == 'l' || src[1] == 'L')
{
if (src[2] < '0' || src[2] > '9')
return NULL; /* Assume no register name but a label starting with 'rl'. */
*mode = CLASS_REG_BYTE;
res = whatreg (preg, src + 2);
if (res == NULL)
return NULL; /* Not a valid register name. */
regno = *preg;
if (regno > 7)
as_bad (_("register rl%d out of range"), regno);
*preg += 8;
}
else if (src[1] == 'q' || src[1] == 'Q')
{
if (src[2] < '0' || src[2] > '9')
return NULL; /* Assume no register name but a label starting with 'rq'. */
*mode = CLASS_REG_QUAD;
res = whatreg (preg, src + 2);
if (res == NULL)
return NULL; /* Not a valid register name. */
regno = *preg;
if (regno > 12)
as_bad (_("register rq%d out of range"), regno);
if (regno & 3)
as_bad (_("register rq%d does not exist"), regno);
}
else
{
if (src[1] < '0' || src[1] > '9')
return NULL; /* Assume no register name but a label starting with 'r'. */
*mode = CLASS_REG_WORD;
res = whatreg (preg, src + 1);
if (res == NULL)
return NULL; /* Not a valid register name. */
regno = *preg;
if (regno > 15)
as_bad (_("register r%d out of range"), regno);
}
}
return res;
}
static char *
parse_exp (char *s, expressionS *op)
{
char *save = input_line_pointer;
char *new_pointer;
input_line_pointer = s;
expression (op);
if (op->X_op == O_absent)
as_bad (_("missing operand"));
new_pointer = input_line_pointer;
input_line_pointer = save;
return new_pointer;
}
/* The many forms of operand:
<rb>
<r>
<rr>
<rq>
@r
#exp
exp
exp(r)
r(#exp)
r(r)
*/
static char *
checkfor (char *ptr, char what)
{
if (*ptr == what)
ptr++;
else
as_bad (_("expected %c"), what);
return ptr;
}
/* Make sure the mode supplied is the size of a word. */
static void
regword (int mode, const char *string)
{
int ok;
ok = CLASS_REG_WORD;
if (ok != mode)
{
as_bad (_("register is wrong size for a word %s"), string);
}
}
/* Make sure the mode supplied is the size of an address. */
static void
regaddr (int mode, const char *string)
{
int ok;
ok = segmented_mode ? CLASS_REG_LONG : CLASS_REG_WORD;
if (ok != mode)
{
as_bad (_("register is wrong size for address %s"), string);
}
}
struct ctrl_names {
int value;
const char *name;
};
static struct ctrl_names ctrl_table[] = {
{ 0x1, "flags" }, /* ldctlb only. */
{ 0x2, "fcw" }, /* ldctl only. Applies to all remaining control registers. */
{ 0x3, "refresh" },
{ 0x4, "psapseg" },
{ 0x5, "psapoff" },
{ 0x5, "psap" },
{ 0x6, "nspseg" },
{ 0x7, "nspoff" },
{ 0x7, "nsp" },
{ 0 , 0 }
};
static void
get_ctrl_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED)
{
char *src = *ptr;
int i, l;
while (*src == ' ')
src++;
mode->mode = CLASS_CTRL;
for (i = 0; ctrl_table[i].name; i++)
{
l = strlen (ctrl_table[i].name);
if (! strncasecmp (ctrl_table[i].name, src, l))
{
the_ctrl = ctrl_table[i].value;
if (*(src + l) && *(src + l) != ',')
break;
*ptr = src + l; /* Valid control name found: "consume" it. */
return;
}
}
the_ctrl = 0;
}
struct flag_names {
int value;
const char *name;
};
static struct flag_names flag_table[] = {
{ 0x1, "P" },
{ 0x1, "V" },
{ 0x2, "S" },
{ 0x4, "Z" },
{ 0x8, "C" },
{ 0x0, "+" },
{ 0x0, "," },
{ 0, 0 }
};
static void
get_flags_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED)
{
char *src = *ptr;
char c;
int i;
int j;
while (*src == ' ')
src++;
mode->mode = CLASS_FLAGS;
the_flags = 0;
for (j = 0; j <= 9; j++)
{
if (!src[j])
goto done;
c = TOUPPER(src[j]);
for (i = 0; flag_table[i].name; i++)
{
if (flag_table[i].name[0] == c)
{
the_flags = the_flags | flag_table[i].value;
goto match;
}
}
goto done;
match:
;
}
done:
*ptr = src + j;
}
struct interrupt_names {
int value;
const char *name;
};
static struct interrupt_names intr_table[] = {
{ 0x1, "nvi" },
{ 0x2, "vi" },
{ 0x3, "both" },
{ 0x3, "all" },
{ 0, 0 }
};
static void
get_interrupt_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED)
{
char *src = *ptr;
int i, l;
while (*src == ' ')
src++;
mode->mode = CLASS_IMM;
the_interrupt = 0;
while (*src)
{
for (i = 0; intr_table[i].name; i++)
{
l = strlen (intr_table[i].name);
if (! strncasecmp (intr_table[i].name, src, l))
{
the_interrupt |= intr_table[i].value;
if (*(src + l) && *(src + l) != ',')
{
*ptr = src + l;
invalid:
as_bad (_("unknown interrupt %s"), src);
while (**ptr && ! is_end_of_line[(unsigned char) **ptr])
(*ptr)++; /* Consume rest of line. */
return;
}
src += l;
if (! *src)
{
*ptr = src;
return;
}
}
}
if (*src == ',')
src++;
else
{
*ptr = src;
goto invalid;
}
}
/* No interrupt type specified, opcode won't do anything. */
as_warn (_("opcode has no effect"));
the_interrupt = 0x0;
}
struct cc_names {
int value;
const char *name;
};
static struct cc_names table[] = {
{ 0x0, "f" },
{ 0x1, "lt" },
{ 0x2, "le" },
{ 0x3, "ule" },
{ 0x4, "ov/pe" },
{ 0x4, "ov" },
{ 0x4, "pe/ov" },
{ 0x4, "pe" },
{ 0x5, "mi" },
{ 0x6, "eq" },
{ 0x6, "z" },
{ 0x7, "c/ult" },
{ 0x7, "c" },
{ 0x7, "ult/c" },
{ 0x7, "ult" },
{ 0x8, "t" },
{ 0x9, "ge" },
{ 0xa, "gt" },
{ 0xb, "ugt" },
{ 0xc, "nov/po" },
{ 0xc, "nov" },
{ 0xc, "po/nov" },
{ 0xc, "po" },
{ 0xd, "pl" },
{ 0xe, "ne" },
{ 0xe, "nz" },
{ 0xf, "nc/uge" },
{ 0xf, "nc" },
{ 0xf, "uge/nc" },
{ 0xf, "uge" },
{ 0 , 0 }
};
static void
get_cc_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED)
{
char *src = *ptr;
int i, l;
while (*src == ' ')
src++;
mode->mode = CLASS_CC;
for (i = 0; table[i].name; i++)
{
l = strlen (table[i].name);
if (! strncasecmp (table[i].name, src, l))
{
the_cc = table[i].value;
if (*(src + l) && *(src + l) != ',')
break;
*ptr = src + l; /* Valid cc found: "consume" it. */
return;
}
}
the_cc = 0x8; /* Not recognizing the cc defaults to t. (Assuming no cc present.) */
}
static void
get_operand (char **ptr, struct z8k_op *mode, unsigned int dst ATTRIBUTE_UNUSED)
{
char *src = *ptr;
char *end;
mode->mode = 0;
while (*src == ' ')
src++;
if (*src == '#')
{
mode->mode = CLASS_IMM;
imm_operand = &(mode->exp);
src = parse_exp (src + 1, &(mode->exp));
}
else if (*src == '@')
{
mode->mode = CLASS_IR;
src = parse_reg (src + 1, &mode->regsize, &mode->reg);
}
else
{
unsigned int regn;
end = parse_reg (src, &mode->mode, &regn);
if (end)
{
int nw;
unsigned int nr;
src = end;
if (*src == '(')
{
src++;
end = parse_reg (src, &nw, &nr);
if (end)
{
/* Got Ra(Rb). */
src = end;
if (*src != ')')
as_bad (_("Missing ) in ra(rb)"));
else
src++;
regaddr (mode->mode, "ra(rb) ra");
mode->mode = CLASS_BX;
mode->reg = regn;
mode->x_reg = nr;
reg[ARG_RX] = nr;
}
else
{
/* Got Ra(disp). */
if (*src == '#')
src++;
src = parse_exp (src, &(mode->exp));
src = checkfor (src, ')');
mode->mode = CLASS_BA;
mode->reg = regn;
mode->x_reg = 0;
imm_operand = &(mode->exp);
}
}
else
{
mode->reg = regn;
mode->x_reg = 0;
}
}
else
{
/* No initial reg. */
src = parse_exp (src, &(mode->exp));
if (*src == '(')
{
src++;
end = parse_reg (src, &(mode->mode), &regn);
regword (mode->mode, "addr(Ra) ra");
mode->mode = CLASS_X;
mode->reg = regn;
mode->x_reg = 0;
da_operand = &(mode->exp);
src = checkfor (end, ')');
}
else
{
/* Just an address. */
mode->mode = CLASS_DA;
mode->reg = 0;
mode->x_reg = 0;
da_operand = &(mode->exp);
}
}
}
*ptr = src;
}
static char *
get_operands (const opcode_entry_type *opcode, char *op_end, op_type *operand)
{
char *ptr = op_end;
char *savptr;
switch (opcode->noperands)
{
case 0:
operand[0].mode = 0;
operand[1].mode = 0;
while (*ptr == ' ')
ptr++;
break;
case 1:
if (opcode->arg_info[0] == CLASS_CC)
{
get_cc_operand (&ptr, operand + 0, 0);
while (*ptr == ' ')
ptr++;
if (*ptr && ! is_end_of_line[(unsigned char) *ptr])
{
as_bad (_("invalid condition code '%s'"), ptr);
while (*ptr && ! is_end_of_line[(unsigned char) *ptr])
ptr++; /* Consume rest of line. */
}
}
else if (opcode->arg_info[0] == CLASS_FLAGS)
{
get_flags_operand (&ptr, operand + 0, 0);
while (*ptr == ' ')
ptr++;
if (*ptr && ! is_end_of_line[(unsigned char) *ptr])
{
as_bad (_("invalid flag '%s'"), ptr);
while (*ptr && ! is_end_of_line[(unsigned char) *ptr])
ptr++; /* Consume rest of line. */
}
}
else if (opcode->arg_info[0] == (CLASS_IMM + (ARG_IMM2)))
get_interrupt_operand (&ptr, operand + 0, 0);
else
get_operand (&ptr, operand + 0, 0);
operand[1].mode = 0;
break;
case 2:
savptr = ptr;
if (opcode->arg_info[0] == CLASS_CC)
{
get_cc_operand (&ptr, operand + 0, 0);
while (*ptr == ' ')
ptr++;
if (*ptr != ',' && strchr (ptr + 1, ','))
{
savptr = ptr;
while (*ptr != ',')
ptr++;
*ptr = 0;
ptr++;
as_bad (_("invalid condition code '%s'"), savptr);
}
}
else if (opcode->arg_info[0] == CLASS_CTRL)
{
get_ctrl_operand (&ptr, operand + 0, 0);
if (the_ctrl == 0)
{
ptr = savptr;
get_operand (&ptr, operand + 0, 0);
if (ptr == 0)
return NULL;
if (*ptr == ',')
ptr++;
get_ctrl_operand (&ptr, operand + 1, 1);
if (the_ctrl == 0)
return NULL;
return ptr;
}
}
else
get_operand (&ptr, operand + 0, 0);
if (ptr == 0)
return NULL;
if (*ptr == ',')
ptr++;
get_operand (&ptr, operand + 1, 1);
break;
case 3:
get_operand (&ptr, operand + 0, 0);
if (*ptr == ',')
ptr++;
get_operand (&ptr, operand + 1, 1);
if (*ptr == ',')
ptr++;
get_operand (&ptr, operand + 2, 2);
break;
case 4:
get_operand (&ptr, operand + 0, 0);
if (*ptr == ',')
ptr++;
get_operand (&ptr, operand + 1, 1);
if (*ptr == ',')
ptr++;
get_operand (&ptr, operand + 2, 2);
if (*ptr == ',')
ptr++;
get_cc_operand (&ptr, operand + 3, 3);
break;
default:
abort ();
}
return ptr;
}
/* Passed a pointer to a list of opcodes which use different
addressing modes. Return the opcode which matches the opcodes
provided. */
static opcode_entry_type *
get_specific (opcode_entry_type *opcode, op_type *operands)
{
opcode_entry_type *this_try = opcode;
int found = 0;
unsigned int noperands = opcode->noperands;
unsigned int this_index = opcode->idx;
while (this_index == opcode->idx && !found)
{
unsigned int i;
this_try = opcode++;
for (i = 0; i < noperands; i++)
{
unsigned int mode = operands[i].mode;
if (((mode & CLASS_MASK) == CLASS_IR) && ((this_try->arg_info[i] & CLASS_MASK) == CLASS_IRO))
{
mode = operands[i].mode = (operands[i].mode & ~CLASS_MASK) | CLASS_IRO;
}
if ((mode & CLASS_MASK) != (this_try->arg_info[i] & CLASS_MASK))
{
/* It could be a pc rel operand, if this is a da mode
and we like disps, then insert it. */
if (mode == CLASS_DA && this_try->arg_info[i] == CLASS_DISP)
{
/* This is the case. */
operands[i].mode = CLASS_DISP;
}
else if (mode == CLASS_BA && this_try->arg_info[i])
{
/* Can't think of a way to turn what we've been
given into something that's OK. */
goto fail;
}
else if (this_try->arg_info[i] & CLASS_PR)
{
if (mode == CLASS_REG_LONG && segmented_mode)
{
/* OK. */
}
else if (mode == CLASS_REG_WORD && !segmented_mode)
{
/* OK. */
}
else
goto fail;
}
else
goto fail;
}
switch (mode & CLASS_MASK)
{
default:
break;
case CLASS_IRO:
if (operands[i].regsize != CLASS_REG_WORD)
as_bad (_("invalid indirect register size"));
reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg;
break;
case CLASS_IR:
if ((segmented_mode && operands[i].regsize != CLASS_REG_LONG)
|| (!segmented_mode && operands[i].regsize != CLASS_REG_WORD))
as_bad (_("invalid indirect register size"));
reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg;
break;
case CLASS_X:
case CLASS_BA:
case CLASS_BX:
case CLASS_DISP:
case CLASS_REG:
case CLASS_REG_WORD:
case CLASS_REG_BYTE:
case CLASS_REG_QUAD:
case CLASS_REG_LONG:
case CLASS_REGN0:
reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg;
break;
case CLASS_CTRL:
if (this_try->opcode == OPC_ldctlb && the_ctrl != 1)
as_bad (_("invalid control register name"));
break;
}
}
found = 1;
fail:
;
}
if (found)
return this_try;
else
return 0;
}
static unsigned char buffer[20];
static void
newfix (int ptr, bfd_reloc_code_real_type type, int size, expressionS *operand)
{
fixS *fixP;
/* Size is in nibbles. */
if (operand->X_add_symbol
|| operand->X_op_symbol
|| operand->X_add_number)
{
int is_pcrel;
switch(type)
{
case BFD_RELOC_8_PCREL:
case BFD_RELOC_Z8K_CALLR:
case BFD_RELOC_Z8K_DISP7:
is_pcrel = 1;
break;
default:
is_pcrel = 0;
break;
}
fixP = fix_new_exp (frag_now, ptr, size / 2,
operand, is_pcrel, type);
if (is_pcrel)
fixP->fx_no_overflow = 1;
}
}
static unsigned char *
apply_fix (unsigned char *ptr, bfd_reloc_code_real_type type,
expressionS *operand, int size)
{
long n = operand->X_add_number;
/* size is in nibbles. */
newfix ((ptr - buffer) / 2, type, size + 1, operand);
if (type == BFD_RELOC_Z8K_DISP7)
{
/* 2 nibbles, but most significant bit is part of the opcode == 7 bits. */
*ptr++ = (n >> 4) & 7;
*ptr++ = n >> 0;
}
else
{
switch (size)
{
case 8: /* 8 nibbles == 32 bits. */
*ptr++ = n >> 28;
*ptr++ = n >> 24;
*ptr++ = n >> 20;
*ptr++ = n >> 16;
/* Fall through. */
case 4: /* 4 nibbles == 16 bits. */
*ptr++ = n >> 12;
*ptr++ = n >> 8;
/* Fall through. */
case 2:
*ptr++ = n >> 4;
/* Fall through. */
case 1:
*ptr++ = n >> 0;
break;
}
}
return ptr;
}
/* Now we know what sort of opcodes it is. Let's build the bytes. */
static void
build_bytes (opcode_entry_type *this_try, struct z8k_op *operand ATTRIBUTE_UNUSED)
{
unsigned char *output_ptr = buffer;
int c;
int nibble;
unsigned int *class_ptr;
frag_wane (frag_now);
frag_new (0);
if (frag_room () < 8)
frag_grow (8); /* Make room for maximum instruction size. */
memset (buffer, 0, sizeof (buffer));
class_ptr = this_try->byte_info;
for (nibble = 0; (c = *class_ptr++); nibble++)
{
switch (c & CLASS_MASK)
{
default:
abort ();
case CLASS_ADDRESS:
/* Direct address, we don't cope with the SS mode right now. */
if (segmented_mode)
{
/* da_operand->X_add_number |= 0x80000000; -- Now set at relocation time. */
output_ptr = apply_fix (output_ptr, BFD_RELOC_32, da_operand, 8);
}
else
{
output_ptr = apply_fix (output_ptr, BFD_RELOC_16, da_operand, 4);
}
da_operand = 0;
break;
case CLASS_DISP8:
/* pc rel 8 bit */
output_ptr = apply_fix (output_ptr, BFD_RELOC_8_PCREL, da_operand, 2);
da_operand = 0;
break;
case CLASS_0DISP7:
/* pc rel 7 bit */
*output_ptr = 0;
output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_DISP7, da_operand, 2);
da_operand = 0;
break;
case CLASS_1DISP7:
/* pc rel 7 bit */
*output_ptr = 0x80;
output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_DISP7, da_operand, 2);
output_ptr[-2] = 0x8;
da_operand = 0;
break;
case CLASS_BIT_1OR2:
*output_ptr = c & 0xf;
if (imm_operand)
{
if (imm_operand->X_add_number == 2)
*output_ptr |= 2;
else if (imm_operand->X_add_number != 1)
as_bad (_("immediate must be 1 or 2"));
}
else
as_bad (_("immediate 1 or 2 expected"));
output_ptr++;
break;
case CLASS_CC:
*output_ptr++ = the_cc;
break;
case CLASS_0CCC:
if (the_ctrl < 2 || the_ctrl > 7)
as_bad (_("invalid control register name"));
*output_ptr++ = the_ctrl;
break;
case CLASS_1CCC:
if (the_ctrl < 2 || the_ctrl > 7)
as_bad (_("invalid control register name"));
*output_ptr++ = the_ctrl | 0x8;
break;
case CLASS_00II:
*output_ptr++ = (~the_interrupt & 0x3);
break;
case CLASS_01II:
*output_ptr++ = (~the_interrupt & 0x3) | 0x4;
break;
case CLASS_FLAGS:
*output_ptr++ = the_flags;
break;
case CLASS_IGNORE:
case CLASS_BIT:
*output_ptr++ = c & 0xf;
break;
case CLASS_REGN0:
if (reg[c & 0xf] == 0)
as_bad (_("can't use R0 here"));
/* Fall through. */
case CLASS_REG:
case CLASS_REG_BYTE:
case CLASS_REG_WORD:
case CLASS_REG_LONG:
case CLASS_REG_QUAD:
/* Insert bit pattern of right reg. */
*output_ptr++ = reg[c & 0xf];
break;
case CLASS_DISP:
switch (c & ARG_MASK)
{
case ARG_DISP12:
output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_CALLR, da_operand, 4);
break;
case ARG_DISP16:
output_ptr = apply_fix (output_ptr, BFD_RELOC_16_PCREL, da_operand, 4);
break;
default:
output_ptr = apply_fix (output_ptr, BFD_RELOC_16, da_operand, 4);
}
da_operand = 0;
break;
case CLASS_IMM:
{
switch (c & ARG_MASK)
{
case ARG_NIM4:
if (imm_operand->X_add_number > 15)
as_bad (_("immediate value out of range"));
imm_operand->X_add_number = -imm_operand->X_add_number;
output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_IMM4L, imm_operand, 1);
break;
/*case ARG_IMMNMINUS1: not used. */
case ARG_IMM4M1:
imm_operand->X_add_number--;
/* Fall through. */
case ARG_IMM4:
if (imm_operand->X_add_number > 15)
as_bad (_("immediate value out of range"));
output_ptr = apply_fix (output_ptr, BFD_RELOC_Z8K_IMM4L, imm_operand, 1);
break;
case ARG_NIM8:
imm_operand->X_add_number = -imm_operand->X_add_number;
/* Fall through. */
case ARG_IMM8:
output_ptr = apply_fix (output_ptr, BFD_RELOC_8, imm_operand, 2);
break;
case ARG_IMM16:
output_ptr = apply_fix (output_ptr, BFD_RELOC_16, imm_operand, 4);
break;
case ARG_IMM32:
output_ptr = apply_fix (output_ptr, BFD_RELOC_32, imm_operand, 8);
break;
default:
abort ();
}
}
}
}
/* Copy from the nibble buffer into the frag. */
{
int length = (output_ptr - buffer) / 2;
unsigned char *src = buffer;
unsigned char *fragp = (unsigned char *) frag_more (length);
while (src < output_ptr)
{
*fragp = ((src[0] & 0xf) << 4) | (src[1] & 0xf);
src += 2;
fragp++;
}
}
}
/* This is the guts of the machine-dependent assembler. STR points to a
machine dependent instruction. This function is supposed to emit
the frags/bytes it assembles to. */
void
md_assemble (char *str)
{
char c;
char *op_start;
char *op_end;
struct z8k_op operand[4];
opcode_entry_type *opcode;
/* Drop leading whitespace. */
while (*str == ' ')
str++;
/* Find the op code end. */
for (op_start = op_end = str;
*op_end != 0 && *op_end != ' ' && ! is_end_of_line[(unsigned char) *op_end];
op_end++)
;
if (op_end == op_start)
{
as_bad (_("can't find opcode "));
}
c = *op_end;
*op_end = 0; /* Zero-terminate op code string for str_hash_find() call. */
opcode = (opcode_entry_type *) str_hash_find (opcode_hash_control, op_start);
if (opcode == NULL)
{
as_bad (_("unknown opcode"));
return;
}
*op_end = c; /* Restore original string. */
if (opcode->opcode == 250)
{
pseudo_typeS *p;
char oc;
char *old = input_line_pointer;
/* Was really a pseudo op. */
input_line_pointer = op_end;
oc = *old;
*old = '\n';
while (*input_line_pointer == ' ')
input_line_pointer++;
p = (pseudo_typeS *) (opcode->func);
(p->poc_handler) (p->poc_val);
input_line_pointer = old;
*old = oc;
}
else
{
char *new_input_line_pointer;
new_input_line_pointer = get_operands (opcode, op_end, operand);
if (new_input_line_pointer)
{
input_line_pointer = new_input_line_pointer;
opcode = get_specific (opcode, operand);
}
if (new_input_line_pointer == NULL || opcode == NULL)
{
/* Couldn't find an opcode which matched the operands. */
char *where = frag_more (2);
where[0] = 0x0;
where[1] = 0x0;
as_bad (_("Can't find opcode to match operands"));
return;
}
build_bytes (opcode, operand);
}
}
/* We have no need to default values of symbols. */
symbolS *
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
{
return 0;
}
/* Various routines to kill one day. */
const char *
md_atof (int type, char *litP, int *sizeP)
{
return ieee_md_atof (type, litP, sizeP, true);
}
const char md_shortopts[] = "z:";
const struct option md_longopts[] =
{
#define OPTION_RELAX (OPTION_MD_BASE)
{"linkrelax", no_argument, NULL, OPTION_RELAX},
{NULL, no_argument, NULL, 0}
};
const size_t md_longopts_size = sizeof (md_longopts);
int
md_parse_option (int c, const char *arg)
{
switch (c)
{
case 'z':
if (!strcmp (arg, "8001"))
z8k_target_from_cmdline = 2;
else if (!strcmp (arg, "8002"))
z8k_target_from_cmdline = 1;
else
{
as_bad (_("invalid architecture -z%s"), arg);
return 0;
}
break;
case OPTION_RELAX:
linkrelax = 1;
break;
default:
return 0;
}
return 1;
}
void
md_show_usage (FILE *stream)
{
fprintf (stream, _("\
Z8K options:\n\
-z8001 generate segmented code\n\
-z8002 generate unsegmented code\n\
-linkrelax create linker relaxable code\n"));
}
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
segT sec ATTRIBUTE_UNUSED,
fragS *fragP ATTRIBUTE_UNUSED)
{
printf (_("call to md_convert_frag\n"));
abort ();
}
/* Generate a machine dependent reloc from a fixup. */
arelent*
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED,
fixS *fixp ATTRIBUTE_UNUSED)
{
arelent *reloc;
reloc = XNEW (arelent);
reloc->sym_ptr_ptr = XNEW (asymbol *);
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
reloc->addend = fixp->fx_offset;
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
if (! reloc->howto)
{
as_bad_where (fixp->fx_file, fixp->fx_line,
_("Cannot represent %s relocation in object file"),
bfd_get_reloc_code_name (fixp->fx_r_type));
abort ();
}
return reloc;
}
valueT
md_section_align (segT seg, valueT size)
{
int align = bfd_section_alignment (seg);
valueT mask = ((valueT) 1 << align) - 1;
return (size + mask) & ~mask;
}
/* Attempt to simplify or eliminate a fixup. To indicate that a fixup
has been eliminated, set fix->fx_done. If fix->fx_addsy is non-NULL,
we will have to generate a reloc entry. */
void
md_apply_fix (fixS *fixP, valueT *valP, segT segment ATTRIBUTE_UNUSED)
{
long val = * (long *) valP;
char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;
switch (fixP->fx_r_type)
{
case BFD_RELOC_Z8K_IMM4L:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
buf[0] = (buf[0] & 0xf0) | (val & 0xf);
break;
case BFD_RELOC_8:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
*buf++ = val;
break;
case BFD_RELOC_16:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
*buf++ = (val >> 8);
*buf++ = val;
}
break;
case BFD_RELOC_32:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
*buf++ = (val >> 24);
*buf++ = (val >> 16);
*buf++ = (val >> 8);
*buf++ = val;
}
break;
case BFD_RELOC_8_PCREL:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
if (val & 1)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("cannot branch to odd address"));
val /= 2;
if (val > 127 || val < -128)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("relative jump out of range"));
*buf++ = val;
fixP->fx_no_overflow = 1;
fixP->fx_done = 1;
}
break;
case BFD_RELOC_16_PCREL:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
val = val - fixP->fx_frag->fr_address + fixP->fx_where - fixP->fx_size;
if (val > 32767 || val < -32768)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("relative address out of range"));
*buf++ = (val >> 8);
*buf++ = val;
fixP->fx_no_overflow = 1;
fixP->fx_done = 1;
}
break;
case BFD_RELOC_Z8K_CALLR:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
if (val & 1)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("cannot branch to odd address"));
if (val > 4096 || val < -4095)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("relative call out of range"));
val = -val / 2;
*buf = (*buf & 0xf0) | ((val >> 8) & 0xf);
buf++;
*buf++ = val & 0xff;
fixP->fx_no_overflow = 1;
fixP->fx_done = 1;
}
break;
case BFD_RELOC_Z8K_DISP7:
if (fixP->fx_addsy)
{
fixP->fx_no_overflow = 1;
fixP->fx_done = 0;
}
else
{
if (val & 1)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("cannot branch to odd address"));
val /= 2;
if (val > 0 || val < -127)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("relative jump out of range"));
*buf = (*buf & 0x80) | (-val & 0x7f);
fixP->fx_no_overflow = 1;
fixP->fx_done = 1;
}
break;
default:
printf(_("md_apply_fix: unknown r_type 0x%x\n"), fixP->fx_r_type);
abort ();
}
if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0)
fixP->fx_done = 1;
}
int
md_estimate_size_before_relax (fragS *fragP ATTRIBUTE_UNUSED,
segT segment_type ATTRIBUTE_UNUSED)
{
printf (_("call to md_estimate_size_before_relax\n"));
abort ();
}
/* Put number into target byte order. */
void
md_number_to_chars (char *ptr, valueT use, int nbytes)
{
number_to_chars_bigendian (ptr, use, nbytes);
}
/* On the Z8000, a PC-relative offset is relative to the address of the
instruction plus its size. */
long
md_pcrel_from (fixS *fixP)
{
return fixP->fx_size + fixP->fx_where + fixP->fx_frag->fr_address;
}
void
tc_coff_symbol_emit_hook (symbolS *s ATTRIBUTE_UNUSED)
{
}