blob: 04a799a5a690bc9e3a7ee844a2d58b3a794f8b81 [file] [log] [blame]
/* tc-mmix.c -- Assembler for Don Knuth's MMIX.
Copyright (C) 2001-2021 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. */
/* Knuth's assembler mmixal does not provide a relocatable format; mmo is
to be considered a final link-format. In the final link, we make mmo,
but for relocatable files, we use ELF.
One goal is to provide a superset of what mmixal does, including
compatible syntax, but the main purpose is to serve GCC. */
#include "as.h"
#include <limits.h>
#include "subsegs.h"
#include "elf/mmix.h"
#include "opcode/mmix.h"
#include "safe-ctype.h"
#include "dwarf2dbg.h"
#include "obstack.h"
/* Something to describe what we need to do with a fixup before output,
for example assert something of what it became or make a relocation. */
enum mmix_fixup_action
{
mmix_fixup_byte,
mmix_fixup_register,
mmix_fixup_register_or_adjust_for_byte
};
static int get_spec_regno (char *);
static int get_operands (int, char *, expressionS *);
static int get_putget_operands (struct mmix_opcode *, char *, expressionS *);
static void s_prefix (int);
static void s_greg (int);
static void s_loc (int);
static void s_bspec (int);
static void s_espec (int);
static void mmix_s_local (int);
static void mmix_greg_internal (char *);
static void mmix_set_geta_branch_offset (char *, offsetT);
static void mmix_set_jmp_offset (char *, offsetT);
static void mmix_fill_nops (char *, int);
static int cmp_greg_symbol_fixes (const void *, const void *);
static int cmp_greg_val_greg_symbol_fixes (const void *, const void *);
static void mmix_handle_rest_of_empty_line (void);
static void mmix_discard_rest_of_line (void);
static void mmix_byte (void);
static void mmix_cons (int);
/* Continue the tradition of symbols.c; use control characters to enforce
magic. These are used when replacing e.g. 8F and 8B so we can handle
such labels correctly with the common parser hooks. */
#define MAGIC_FB_BACKWARD_CHAR '\003'
#define MAGIC_FB_FORWARD_CHAR '\004'
/* Copy the location of a frag to a fix. */
#define COPY_FR_WHERE_TO_FX(FRAG, FIX) \
do \
{ \
(FIX)->fx_file = (FRAG)->fr_file; \
(FIX)->fx_line = (FRAG)->fr_line; \
} \
while (0)
const char *md_shortopts = "x";
static int current_fb_label = -1;
static char *pending_label = NULL;
static bfd_vma lowest_text_loc = (bfd_vma) -1;
static int text_has_contents = 0;
/* The alignment of the previous instruction, and a boolean for whether we
want to avoid aligning the next WYDE, TETRA, OCTA or insn. */
static int last_alignment = 0;
static int want_unaligned = 0;
static bfd_vma lowest_data_loc = (bfd_vma) -1;
static int data_has_contents = 0;
/* The fragS of the instruction being assembled. Only valid from within
md_assemble. */
fragS *mmix_opcode_frag = NULL;
/* Raw GREGs as appearing in input. These may be fewer than the number
after relaxing. */
static int n_of_raw_gregs = 0;
static struct
{
char *label;
expressionS exp;
} mmix_raw_gregs[MAX_GREGS];
static struct loc_assert_s
{
segT old_seg;
symbolS *loc_sym;
fragS *frag;
struct loc_assert_s *next;
} *loc_asserts = NULL;
/* Fixups for all unique GREG registers. We store the fixups here in
md_convert_frag, then we use the array to convert
BFD_RELOC_MMIX_BASE_PLUS_OFFSET fixups in tc_gen_reloc. The index is
just a running number and is not supposed to be correlated to a
register number. */
static fixS *mmix_gregs[MAX_GREGS];
static int n_of_cooked_gregs = 0;
/* Pointing to the register section we use for output. */
static asection *real_reg_section;
/* For each symbol; unknown or section symbol, we keep a list of GREG
definitions sorted on increasing offset. It seems no use keeping count
to allocate less room than the maximum number of gregs when we've found
one for a section or symbol. */
struct mmix_symbol_gregs
{
int n_gregs;
struct mmix_symbol_greg_fixes
{
fixS *fix;
/* A signed type, since we may have GREGs pointing slightly before the
contents of a section. */
offsetT offs;
} greg_fixes[MAX_GREGS];
};
/* Should read insert a colon on something that starts in column 0 on
this line? */
static int label_without_colon_this_line = 1;
/* Should we automatically expand instructions into multiple insns in
order to generate working code? */
static int expand_op = 1;
/* Should we warn when expanding operands? FIXME: test-cases for when -x
is absent. */
static int warn_on_expansion = 1;
/* Should we merge non-zero GREG register definitions? */
static int merge_gregs = 1;
/* Should we pass on undefined BFD_RELOC_MMIX_BASE_PLUS_OFFSET relocs
(missing suitable GREG definitions) to the linker? */
static int allocate_undefined_gregs_in_linker = 0;
/* Should we emit built-in symbols? */
static int predefined_syms = 1;
/* Should we allow anything but the listed special register name
(e.g. equated symbols)? */
static int equated_spec_regs = 1;
/* Do we require standard GNU syntax? */
int mmix_gnu_syntax = 0;
/* Do we globalize all symbols? */
int mmix_globalize_symbols = 0;
/* When expanding insns, do we want to expand PUSHJ as a call to a stub
(or else as a series of insns)? */
int pushj_stubs = 1;
/* Do we know that the next semicolon is at the end of the operands field
(in mmixal mode; constant 1 in GNU mode)? */
int mmix_next_semicolon_is_eoln = 1;
/* Do we have a BSPEC in progress? */
static int doing_bspec = 0;
static const char *bspec_file;
static unsigned int bspec_line;
struct option md_longopts[] =
{
#define OPTION_RELAX (OPTION_MD_BASE)
#define OPTION_NOEXPAND (OPTION_RELAX + 1)
#define OPTION_NOMERGEGREG (OPTION_NOEXPAND + 1)
#define OPTION_NOSYMS (OPTION_NOMERGEGREG + 1)
#define OPTION_GNU_SYNTAX (OPTION_NOSYMS + 1)
#define OPTION_GLOBALIZE_SYMBOLS (OPTION_GNU_SYNTAX + 1)
#define OPTION_FIXED_SPEC_REGS (OPTION_GLOBALIZE_SYMBOLS + 1)
#define OPTION_LINKER_ALLOCATED_GREGS (OPTION_FIXED_SPEC_REGS + 1)
#define OPTION_NOPUSHJSTUBS (OPTION_LINKER_ALLOCATED_GREGS + 1)
{"linkrelax", no_argument, NULL, OPTION_RELAX},
{"no-expand", no_argument, NULL, OPTION_NOEXPAND},
{"no-merge-gregs", no_argument, NULL, OPTION_NOMERGEGREG},
{"no-predefined-syms", no_argument, NULL, OPTION_NOSYMS},
{"gnu-syntax", no_argument, NULL, OPTION_GNU_SYNTAX},
{"globalize-symbols", no_argument, NULL, OPTION_GLOBALIZE_SYMBOLS},
{"fixed-special-register-names", no_argument, NULL,
OPTION_FIXED_SPEC_REGS},
{"linker-allocated-gregs", no_argument, NULL,
OPTION_LINKER_ALLOCATED_GREGS},
{"no-pushj-stubs", no_argument, NULL, OPTION_NOPUSHJSTUBS},
{"no-stubs", no_argument, NULL, OPTION_NOPUSHJSTUBS},
{NULL, no_argument, NULL, 0}
};
size_t md_longopts_size = sizeof (md_longopts);
static htab_t mmix_opcode_hash;
/* We use these when implementing the PREFIX pseudo. */
char *mmix_current_prefix;
struct obstack mmix_sym_obstack;
/* For MMIX, we encode the relax_substateT:s (in e.g. fr_substate) as one
bit length, and the relax-type shifted on top of that. There seems to
be no point in making the relaxation more fine-grained; the linker does
that better and we might interfere by changing non-optimal relaxations
into other insns that cannot be relaxed as easily.
Groups for MMIX relaxing:
1. GETA
extra length: zero or three insns.
2. Bcc
extra length: zero or five insns.
3. PUSHJ
extra length: zero or four insns.
Special handling to deal with transition to PUSHJSTUB.
4. JMP
extra length: zero or four insns.
5. GREG
special handling, allocates a named global register unless another
is within reach for all uses.
6. PUSHJSTUB
special handling (mostly) for external references; assumes the
linker will generate a stub if target is no longer than 256k from
the end of the section plus max size of previous stubs. Zero or
four insns. */
#define STATE_GETA (1)
#define STATE_BCC (2)
#define STATE_PUSHJ (3)
#define STATE_JMP (4)
#define STATE_GREG (5)
#define STATE_PUSHJSTUB (6)
/* No fine-grainedness here. */
#define STATE_LENGTH_MASK (1)
#define STATE_ZERO (0)
#define STATE_MAX (1)
/* More descriptive name for convenience. */
/* FIXME: We should start on something different, not MAX. */
#define STATE_UNDF STATE_MAX
/* FIXME: For GREG, we must have other definitions; UNDF == MAX isn't
appropriate; we need it the other way round. This value together with
fragP->tc_frag_data shows what state the frag is in: tc_frag_data
non-NULL means 0, NULL means 8 bytes. */
#define STATE_GREG_UNDF ENCODE_RELAX (STATE_GREG, STATE_ZERO)
#define STATE_GREG_DEF ENCODE_RELAX (STATE_GREG, STATE_MAX)
/* These displacements are relative to the address following the opcode
word of the instruction. The catch-all states have zero for "reach"
and "next" entries. */
#define GETA_0F (65536 * 4 - 8)
#define GETA_0B (-65536 * 4 - 4)
#define GETA_MAX_LEN 4 * 4
#define GETA_3F 0
#define GETA_3B 0
#define BCC_0F GETA_0F
#define BCC_0B GETA_0B
#define BCC_MAX_LEN 6 * 4
#define BCC_5F GETA_3F
#define BCC_5B GETA_3B
#define PUSHJ_0F GETA_0F
#define PUSHJ_0B GETA_0B
#define PUSHJ_MAX_LEN 5 * 4
#define PUSHJ_4F GETA_3F
#define PUSHJ_4B GETA_3B
/* We'll very rarely have sections longer than LONG_MAX, but we'll make a
feeble attempt at getting 64-bit values. */
#define PUSHJSTUB_MAX ((offsetT) (((addressT) -1) >> 1))
#define PUSHJSTUB_MIN (-PUSHJSTUB_MAX - 1)
#define JMP_0F (65536 * 256 * 4 - 8)
#define JMP_0B (-65536 * 256 * 4 - 4)
#define JMP_MAX_LEN 5 * 4
#define JMP_4F 0
#define JMP_4B 0
#define RELAX_ENCODE_SHIFT 1
#define ENCODE_RELAX(what, length) (((what) << RELAX_ENCODE_SHIFT) + (length))
const relax_typeS mmix_relax_table[] =
{
/* Error sentinel (0, 0). */
{1, 1, 0, 0},
/* Unused (0, 1). */
{1, 1, 0, 0},
/* GETA (1, 0). */
{GETA_0F, GETA_0B, 0, ENCODE_RELAX (STATE_GETA, STATE_MAX)},
/* GETA (1, 1). */
{GETA_3F, GETA_3B,
GETA_MAX_LEN - 4, 0},
/* BCC (2, 0). */
{BCC_0F, BCC_0B, 0, ENCODE_RELAX (STATE_BCC, STATE_MAX)},
/* BCC (2, 1). */
{BCC_5F, BCC_5B,
BCC_MAX_LEN - 4, 0},
/* PUSHJ (3, 0). Next state is actually PUSHJSTUB (6, 0). */
{PUSHJ_0F, PUSHJ_0B, 0, ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO)},
/* PUSHJ (3, 1). */
{PUSHJ_4F, PUSHJ_4B,
PUSHJ_MAX_LEN - 4, 0},
/* JMP (4, 0). */
{JMP_0F, JMP_0B, 0, ENCODE_RELAX (STATE_JMP, STATE_MAX)},
/* JMP (4, 1). */
{JMP_4F, JMP_4B,
JMP_MAX_LEN - 4, 0},
/* GREG (5, 0), (5, 1), though the table entry isn't used. */
{0, 0, 0, 0}, {0, 0, 0, 0},
/* PUSHJSTUB (6, 0). PUSHJ (3, 0) uses the range, so we set it to infinite. */
{PUSHJSTUB_MAX, PUSHJSTUB_MIN,
0, ENCODE_RELAX (STATE_PUSHJ, STATE_MAX)},
/* PUSHJSTUB (6, 1) isn't used. */
{0, 0, PUSHJ_MAX_LEN, 0}
};
const pseudo_typeS md_pseudo_table[] =
{
/* Support " .greg sym,expr" syntax. */
{"greg", s_greg, 0},
/* Support " .bspec expr" syntax. */
{"bspec", s_bspec, 1},
/* Support " .espec" syntax. */
{"espec", s_espec, 1},
/* Support " .local $45" syntax. */
{"local", mmix_s_local, 1},
{NULL, 0, 0}
};
const char mmix_comment_chars[] = "%!";
/* A ':' is a valid symbol character in mmixal. It's the prefix
delimiter, but other than that, it works like a symbol character,
except that we strip one off at the beginning of symbols. An '@' is a
symbol by itself (for the current location); space around it must not
be stripped. */
const char mmix_symbol_chars[] = ":@";
const char line_comment_chars[] = "*#";
const char line_separator_chars[] = ";";
const char EXP_CHARS[] = "eE";
const char FLT_CHARS[] = "rf";
/* Fill in the offset-related part of GETA or Bcc. */
static void
mmix_set_geta_branch_offset (char *opcodep, offsetT value)
{
if (value < 0)
{
value += 65536 * 4;
opcodep[0] |= 1;
}
value /= 4;
md_number_to_chars (opcodep + 2, value, 2);
}
/* Fill in the offset-related part of JMP. */
static void
mmix_set_jmp_offset (char *opcodep, offsetT value)
{
if (value < 0)
{
value += 65536 * 256 * 4;
opcodep[0] |= 1;
}
value /= 4;
md_number_to_chars (opcodep + 1, value, 3);
}
/* Fill in NOP:s for the expanded part of GETA/JMP/Bcc/PUSHJ. */
static void
mmix_fill_nops (char *opcodep, int n)
{
int i;
for (i = 0; i < n; i++)
md_number_to_chars (opcodep + i * 4, SWYM_INSN_BYTE << 24, 4);
}
/* See macro md_parse_name in tc-mmix.h. */
int
mmix_current_location (void (*fn) (expressionS *), expressionS *exp)
{
(*fn) (exp);
return 1;
}
/* Get up to three operands, filling them into the exp array.
General idea and code stolen from the tic80 port. */
static int
get_operands (int max_operands, char *s, expressionS *exp)
{
char *p = s;
int numexp = 0;
int nextchar = ',';
while (nextchar == ',')
{
/* Skip leading whitespace */
while (*p == ' ' || *p == '\t')
p++;
/* Check to see if we have any operands left to parse */
if (*p == 0 || *p == '\n' || *p == '\r')
{
break;
}
else if (numexp == max_operands)
{
/* This seems more sane than saying "too many operands". We'll
get here only if the trailing trash starts with a comma. */
as_bad (_("invalid operands"));
mmix_discard_rest_of_line ();
return 0;
}
/* Begin operand parsing at the current scan point. */
input_line_pointer = p;
expression (&exp[numexp]);
if (exp[numexp].X_op == O_illegal)
{
as_bad (_("invalid operands"));
}
else if (exp[numexp].X_op == O_absent)
{
as_bad (_("missing operand"));
}
numexp++;
p = input_line_pointer;
/* Skip leading whitespace */
while (*p == ' ' || *p == '\t')
p++;
nextchar = *p++;
}
/* If we allow "naked" comments, ignore the rest of the line. */
if (nextchar != ',')
{
mmix_handle_rest_of_empty_line ();
input_line_pointer--;
}
/* Mark the end of the valid operands with an illegal expression. */
exp[numexp].X_op = O_illegal;
return (numexp);
}
/* Get the value of a special register, or -1 if the name does not match
one. NAME is a null-terminated string. */
static int
get_spec_regno (char *name)
{
int i;
if (name == NULL)
return -1;
if (*name == ':')
name++;
/* Well, it's a short array and we'll most often just match the first
entry, rJ. */
for (i = 0; mmix_spec_regs[i].name != NULL; i++)
if (strcmp (name, mmix_spec_regs[i].name) == 0)
return mmix_spec_regs[i].number;
return -1;
}
/* For GET and PUT, parse the register names "manually", so we don't use
user labels. */
static int
get_putget_operands (struct mmix_opcode *insn, char *operands,
expressionS *exp)
{
expressionS *expp_reg;
expressionS *expp_sreg;
char *sregp = NULL;
char *sregend = operands;
char *p = operands;
char c = *sregend;
int regno;
/* Skip leading whitespace */
while (*p == ' ' || *p == '\t')
p++;
input_line_pointer = p;
/* Initialize both possible operands to error state, in case we never
get further. */
exp[0].X_op = O_illegal;
exp[1].X_op = O_illegal;
if (insn->operands == mmix_operands_get)
{
expp_reg = &exp[0];
expp_sreg = &exp[1];
expression (expp_reg);
p = input_line_pointer;
/* Skip whitespace */
while (*p == ' ' || *p == '\t')
p++;
if (*p == ',')
{
p++;
/* Skip whitespace */
while (*p == ' ' || *p == '\t')
p++;
sregp = p;
input_line_pointer = sregp;
c = get_symbol_name (&sregp);
sregend = input_line_pointer;
if (c == '"')
++ input_line_pointer;
}
}
else
{
expp_sreg = &exp[0];
expp_reg = &exp[1];
c = get_symbol_name (&sregp);
sregend = input_line_pointer;
restore_line_pointer (c);
p = input_line_pointer;
/* Skip whitespace */
while (*p == ' ' || *p == '\t')
p++;
if (*p == ',')
{
p++;
/* Skip whitespace */
while (*p == ' ' || *p == '\t')
p++;
input_line_pointer = p;
expression (expp_reg);
}
*sregend = 0;
}
regno = get_spec_regno (sregp);
*sregend = c;
/* Let the caller issue errors; we've made sure the operands are
invalid. */
if (expp_reg->X_op != O_illegal
&& expp_reg->X_op != O_absent
&& regno != -1)
{
expp_sreg->X_op = O_register;
expp_sreg->X_add_number = regno + 256;
}
return 2;
}
/* Handle MMIX-specific option. */
int
md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
{
switch (c)
{
case 'x':
warn_on_expansion = 0;
allocate_undefined_gregs_in_linker = 1;
break;
case OPTION_RELAX:
linkrelax = 1;
break;
case OPTION_NOEXPAND:
expand_op = 0;
break;
case OPTION_NOMERGEGREG:
merge_gregs = 0;
break;
case OPTION_NOSYMS:
predefined_syms = 0;
equated_spec_regs = 0;
break;
case OPTION_GNU_SYNTAX:
mmix_gnu_syntax = 1;
label_without_colon_this_line = 0;
break;
case OPTION_GLOBALIZE_SYMBOLS:
mmix_globalize_symbols = 1;
break;
case OPTION_FIXED_SPEC_REGS:
equated_spec_regs = 0;
break;
case OPTION_LINKER_ALLOCATED_GREGS:
allocate_undefined_gregs_in_linker = 1;
break;
case OPTION_NOPUSHJSTUBS:
pushj_stubs = 0;
break;
default:
return 0;
}
return 1;
}
/* Display MMIX-specific help text. */
void
md_show_usage (FILE * stream)
{
fprintf (stream, _(" MMIX-specific command line options:\n"));
fprintf (stream, _("\
-fixed-special-register-names\n\
Allow only the original special register names.\n"));
fprintf (stream, _("\
-globalize-symbols Make all symbols global.\n"));
fprintf (stream, _("\
-gnu-syntax Turn off mmixal syntax compatibility.\n"));
fprintf (stream, _("\
-relax Create linker relaxable code.\n"));
fprintf (stream, _("\
-no-predefined-syms Do not provide mmixal built-in constants.\n\
Implies -fixed-special-register-names.\n"));
fprintf (stream, _("\
-no-expand Do not expand GETA, branches, PUSHJ or JUMP\n\
into multiple instructions.\n"));
fprintf (stream, _("\
-no-merge-gregs Do not merge GREG definitions with nearby values.\n"));
fprintf (stream, _("\
-linker-allocated-gregs If there's no suitable GREG definition for the\
operands of an instruction, let the linker resolve.\n"));
fprintf (stream, _("\
-x Do not warn when an operand to GETA, a branch,\n\
PUSHJ or JUMP is not known to be within range.\n\
The linker will catch any errors. Implies\n\
-linker-allocated-gregs."));
}
/* Step to end of line, but don't step over the end of the line. */
static void
mmix_discard_rest_of_line (void)
{
while (*input_line_pointer
&& (! is_end_of_line[(unsigned char) *input_line_pointer]
|| TC_EOL_IN_INSN (input_line_pointer)))
input_line_pointer++;
}
/* Act as demand_empty_rest_of_line if we're in strict GNU syntax mode,
otherwise just ignore the rest of the line (and skip the end-of-line
delimiter). */
static void
mmix_handle_rest_of_empty_line (void)
{
if (mmix_gnu_syntax)
demand_empty_rest_of_line ();
else
{
mmix_discard_rest_of_line ();
input_line_pointer++;
}
}
/* Initialize GAS MMIX specifics. */
void
mmix_md_begin (void)
{
int i;
const struct mmix_opcode *opcode;
/* We assume nobody will use this, so don't allocate any room. */
obstack_begin (&mmix_sym_obstack, 0);
/* This will break the day the "lex" thingy changes. For now, it's the
only way to make ':' part of a name, and a name beginner. */
lex_type[':'] = (LEX_NAME | LEX_BEGIN_NAME);
mmix_opcode_hash = str_htab_create ();
real_reg_section
= bfd_make_section_old_way (stdoutput, MMIX_REG_SECTION_NAME);
for (opcode = mmix_opcodes; opcode->name; opcode++)
str_hash_insert (mmix_opcode_hash, opcode->name, opcode, 0);
/* We always insert the ordinary registers 0..255 as registers. */
for (i = 0; i < 256; i++)
{
char buf[16];
/* Alternatively, we could diddle with '$' and the following number,
but keeping the registers as symbols helps keep parsing simple. */
sprintf (buf, "$%d", i);
symbol_table_insert (symbol_new (buf, reg_section,
&zero_address_frag, i));
}
/* Insert mmixal built-in names if allowed. */
if (predefined_syms)
{
for (i = 0; mmix_spec_regs[i].name != NULL; i++)
symbol_table_insert (symbol_new (mmix_spec_regs[i].name,
reg_section,
&zero_address_frag,
mmix_spec_regs[i].number + 256));
/* FIXME: Perhaps these should be recognized as specials; as field
names for those instructions. */
symbol_table_insert (symbol_new ("ROUND_CURRENT", reg_section,
&zero_address_frag, 512));
symbol_table_insert (symbol_new ("ROUND_OFF", reg_section,
&zero_address_frag, 512 + 1));
symbol_table_insert (symbol_new ("ROUND_UP", reg_section,
&zero_address_frag, 512 + 2));
symbol_table_insert (symbol_new ("ROUND_DOWN", reg_section,
&zero_address_frag, 512 + 3));
symbol_table_insert (symbol_new ("ROUND_NEAR", reg_section,
&zero_address_frag, 512 + 4));
}
}
/* Assemble one insn in STR. */
void
md_assemble (char *str)
{
char *operands = str;
char modified_char = 0;
struct mmix_opcode *instruction;
fragS *opc_fragP = NULL;
int max_operands = 3;
/* Note that the struct frag member fr_literal in frags.h is char[], so
I have to make this a plain char *. */
/* unsigned */ char *opcodep = NULL;
expressionS exp[4];
int n_operands = 0;
/* Move to end of opcode. */
for (operands = str;
is_part_of_name (*operands);
++operands)
;
if (ISSPACE (*operands))
{
modified_char = *operands;
*operands++ = '\0';
}
instruction = (struct mmix_opcode *) str_hash_find (mmix_opcode_hash, str);
if (instruction == NULL)
{
as_bad (_("unknown opcode: `%s'"), str);
/* Avoid "unhandled label" errors. */
pending_label = NULL;
return;
}
/* Put back the character after the opcode. */
if (modified_char != 0)
operands[-1] = modified_char;
input_line_pointer = operands;
/* Is this a mmixal pseudodirective? */
if (instruction->type == mmix_type_pseudo)
{
/* For mmixal compatibility, a label for an instruction (and
emitting pseudo) refers to the _aligned_ address. We emit the
label here for the pseudos that don't handle it themselves. When
having an fb-label, emit it here, and increment the counter after
the pseudo. */
switch (instruction->operands)
{
case mmix_operands_loc:
case mmix_operands_byte:
case mmix_operands_prefix:
case mmix_operands_local:
case mmix_operands_bspec:
case mmix_operands_espec:
if (current_fb_label >= 0)
colon (fb_label_name (current_fb_label, 1));
else if (pending_label != NULL)
{
colon (pending_label);
pending_label = NULL;
}
break;
default:
break;
}
/* Some of the pseudos emit contents, others don't. Set a
contents-emitted flag when we emit something into .text */
switch (instruction->operands)
{
case mmix_operands_loc:
/* LOC */
s_loc (0);
break;
case mmix_operands_byte:
/* BYTE */
mmix_byte ();
break;
case mmix_operands_wyde:
/* WYDE */
mmix_cons (2);
break;
case mmix_operands_tetra:
/* TETRA */
mmix_cons (4);
break;
case mmix_operands_octa:
/* OCTA */
mmix_cons (8);
break;
case mmix_operands_prefix:
/* PREFIX */
s_prefix (0);
break;
case mmix_operands_local:
/* LOCAL */
mmix_s_local (0);
break;
case mmix_operands_bspec:
/* BSPEC */
s_bspec (0);
break;
case mmix_operands_espec:
/* ESPEC */
s_espec (0);
break;
default:
BAD_CASE (instruction->operands);
}
/* These are all working like the pseudo functions in read.c:s_...,
in that they step over the end-of-line marker at the end of the
line. We don't want that here. */
input_line_pointer--;
/* Step up the fb-label counter if there was a definition on this
line. */
if (current_fb_label >= 0)
{
fb_label_instance_inc (current_fb_label);
current_fb_label = -1;
}
/* Reset any don't-align-next-datum request, unless this was a LOC
directive. */
if (instruction->operands != mmix_operands_loc)
want_unaligned = 0;
return;
}
/* Not a pseudo; we *will* emit contents. */
if (now_seg == data_section)
{
if (lowest_data_loc != (bfd_vma) -1 && (lowest_data_loc & 3) != 0)
{
if (data_has_contents)
as_bad (_("specified location wasn't TETRA-aligned"));
else if (want_unaligned)
as_bad (_("unaligned data at an absolute location is not supported"));
lowest_data_loc &= ~(bfd_vma) 3;
lowest_data_loc += 4;
}
data_has_contents = 1;
}
else if (now_seg == text_section)
{
if (lowest_text_loc != (bfd_vma) -1 && (lowest_text_loc & 3) != 0)
{
if (text_has_contents)
as_bad (_("specified location wasn't TETRA-aligned"));
else if (want_unaligned)
as_bad (_("unaligned data at an absolute location is not supported"));
lowest_text_loc &= ~(bfd_vma) 3;
lowest_text_loc += 4;
}
text_has_contents = 1;
}
/* After a sequence of BYTEs or WYDEs, we need to get to instruction
alignment. For other pseudos, a ".p2align 2" is supposed to be
inserted by the user. */
if (last_alignment < 2 && ! want_unaligned)
{
frag_align (2, 0, 0);
record_alignment (now_seg, 2);
last_alignment = 2;
}
else
/* Reset any don't-align-next-datum request. */
want_unaligned = 0;
/* For mmixal compatibility, a label for an instruction (and emitting
pseudo) refers to the _aligned_ address. So we have to emit the
label here. */
if (pending_label != NULL)
{
colon (pending_label);
pending_label = NULL;
}
/* We assume that mmix_opcodes keeps having unique mnemonics for each
opcode, so we don't have to iterate over more than one opcode; if the
syntax does not match, then there's a syntax error. */
/* Operands have little or no context and are all comma-separated; it is
easier to parse each expression first. */
switch (instruction->operands)
{
case mmix_operands_reg_yz:
case mmix_operands_pop:
case mmix_operands_regaddr:
case mmix_operands_pushj:
case mmix_operands_get:
case mmix_operands_put:
case mmix_operands_set:
case mmix_operands_save:
case mmix_operands_unsave:
max_operands = 2;
break;
case mmix_operands_sync:
case mmix_operands_jmp:
case mmix_operands_resume:
max_operands = 1;
break;
/* The original 3 is fine for the rest. */
default:
break;
}
/* If this is GET or PUT, and we don't do allow those names to be
equated, we need to parse the names ourselves, so we don't pick up a
user label instead of the special register. */
if (! equated_spec_regs
&& (instruction->operands == mmix_operands_get
|| instruction->operands == mmix_operands_put))
n_operands = get_putget_operands (instruction, operands, exp);
else
n_operands = get_operands (max_operands, operands, exp);
/* If there's a fb-label on the current line, set that label. This must
be done *after* evaluating expressions of operands, since neither a
"1B" nor a "1F" refers to "1H" on the same line. */
if (current_fb_label >= 0)
{
fb_label_instance_inc (current_fb_label);
colon (fb_label_name (current_fb_label, 0));
current_fb_label = -1;
}
/* We also assume that the length of the instruction is at least 4, the
size of an unexpanded instruction. We need a self-contained frag
since we want the relocation to point to the instruction, not the
variant part. */
opcodep = frag_more (4);
mmix_opcode_frag = opc_fragP = frag_now;
frag_now->fr_opcode = opcodep;
/* Mark start of insn for DWARF2 debug features. */
if (OUTPUT_FLAVOR == bfd_target_elf_flavour)
dwarf2_emit_insn (4);
md_number_to_chars (opcodep, instruction->match, 4);
switch (instruction->operands)
{
case mmix_operands_jmp:
if (n_operands == 0 && ! mmix_gnu_syntax)
/* Zeros are in place - nothing needs to be done when we have no
operands. */
break;
/* Add a frag for a JMP relaxation; we need room for max four
extra instructions. We don't do any work around here to check if
we can determine the offset right away. */
if (n_operands != 1 || exp[0].X_op == O_register)
{
as_bad (_("invalid operand to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (expand_op)
frag_var (rs_machine_dependent, 4 * 4, 0,
ENCODE_RELAX (STATE_JMP, STATE_UNDF),
exp[0].X_add_symbol,
exp[0].X_add_number,
opcodep);
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
exp + 0, 1, BFD_RELOC_MMIX_ADDR27);
break;
case mmix_operands_pushj:
/* We take care of PUSHJ in full here. */
if (n_operands != 2
|| ((exp[0].X_op == O_constant || exp[0].X_op == O_register)
&& (exp[0].X_add_number > 255 || exp[0].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[0].X_op == O_register || exp[0].X_op == O_constant)
opcodep[1] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp + 0, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
if (expand_op)
frag_var (rs_machine_dependent, PUSHJ_MAX_LEN - 4, 0,
ENCODE_RELAX (STATE_PUSHJ, STATE_UNDF),
exp[1].X_add_symbol,
exp[1].X_add_number,
opcodep);
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
exp + 1, 1, BFD_RELOC_MMIX_ADDR19);
break;
case mmix_operands_regaddr:
/* GETA/branch: Add a frag for relaxation. We don't do any work
around here to check if we can determine the offset right away. */
if (n_operands != 2 || exp[1].X_op == O_register)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (! expand_op)
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
exp + 1, 1, BFD_RELOC_MMIX_ADDR19);
else if (instruction->type == mmix_type_condbranch)
frag_var (rs_machine_dependent, BCC_MAX_LEN - 4, 0,
ENCODE_RELAX (STATE_BCC, STATE_UNDF),
exp[1].X_add_symbol,
exp[1].X_add_number,
opcodep);
else
frag_var (rs_machine_dependent, GETA_MAX_LEN - 4, 0,
ENCODE_RELAX (STATE_GETA, STATE_UNDF),
exp[1].X_add_symbol,
exp[1].X_add_number,
opcodep);
break;
default:
break;
}
switch (instruction->operands)
{
case mmix_operands_regs:
/* We check the number of operands here, since we're in a
FALLTHROUGH sequence in the next switch. */
if (n_operands != 3 || exp[2].X_op == O_constant)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
/* FALLTHROUGH. */
case mmix_operands_regs_z:
if (n_operands != 3)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
/* FALLTHROUGH. */
case mmix_operands_reg_yz:
case mmix_operands_roundregs_z:
case mmix_operands_roundregs:
case mmix_operands_regs_z_opt:
case mmix_operands_neg:
case mmix_operands_regaddr:
case mmix_operands_get:
case mmix_operands_set:
case mmix_operands_save:
if (n_operands < 1
|| (exp[0].X_op == O_register && exp[0].X_add_number > 255))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[0].X_op == O_register)
opcodep[1] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp + 0, 0, BFD_RELOC_MMIX_REG);
break;
default:
;
}
/* A corresponding once-over for those who take an 8-bit constant as
their first operand. */
switch (instruction->operands)
{
case mmix_operands_pushgo:
/* PUSHGO: X is a constant, but can be expressed as a register.
We handle X here and use the common machinery of T,X,3,$ for
the rest of the operands. */
if (n_operands < 2
|| ((exp[0].X_op == O_constant || exp[0].X_op == O_register)
&& (exp[0].X_add_number > 255 || exp[0].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else if (exp[0].X_op == O_constant || exp[0].X_op == O_register)
opcodep[1] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp + 0, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
break;
case mmix_operands_pop:
if ((n_operands == 0 || n_operands == 1) && ! mmix_gnu_syntax)
break;
/* FALLTHROUGH. */
case mmix_operands_x_regs_z:
if (n_operands < 1
|| (exp[0].X_op == O_constant
&& (exp[0].X_add_number > 255
|| exp[0].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[0].X_op == O_constant)
opcodep[1] = exp[0].X_add_number;
else
/* FIXME: This doesn't bring us unsignedness checking. */
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp + 0, 0, BFD_RELOC_8);
default:
;
}
/* Handle the rest. */
switch (instruction->operands)
{
case mmix_operands_set:
/* SET: Either two registers, "$X,$Y", with Z field as zero, or
"$X,YZ", meaning change the opcode to SETL. */
if (n_operands != 2
|| (exp[1].X_op == O_constant
&& (exp[1].X_add_number > 0xffff || exp[1].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[1].X_op == O_constant)
{
/* There's an ambiguity with "SET $0,Y" when Y isn't defined
yet. To keep things simple, we assume that Y is then a
register, and only change the opcode if Y is defined at this
point.
There's no compatibility problem with mmixal, since it emits
errors if the field is not defined at this point. */
md_number_to_chars (opcodep, SETL_INSN_BYTE, 1);
opcodep[2] = (exp[1].X_add_number >> 8) & 255;
opcodep[3] = exp[1].X_add_number & 255;
break;
}
/* FALLTHROUGH. */
case mmix_operands_x_regs_z:
/* SYNCD: "X,$Y,$Z|Z". */
/* FALLTHROUGH. */
case mmix_operands_regs:
/* Three registers, $X,$Y,$Z. */
/* FALLTHROUGH. */
case mmix_operands_regs_z:
/* Operands "$X,$Y,$Z|Z", number of arguments checked above. */
/* FALLTHROUGH. */
case mmix_operands_pushgo:
/* Operands "$X|X,$Y,$Z|Z", optional Z. */
/* FALLTHROUGH. */
case mmix_operands_regs_z_opt:
/* Operands "$X,$Y,$Z|Z", with $Z|Z being optional, default 0. Any
operands not completely decided yet are postponed to later in
assembly (but not until link-time yet). */
if ((n_operands != 2 && n_operands != 3)
|| (exp[1].X_op == O_register && exp[1].X_add_number > 255)
|| (n_operands == 3
&& ((exp[2].X_op == O_register
&& exp[2].X_add_number > 255
&& mmix_gnu_syntax)
|| (exp[2].X_op == O_constant
&& (exp[2].X_add_number > 255
|| exp[2].X_add_number < 0)))))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (n_operands == 2)
{
symbolS *sym;
fixS *tmpfixP;
/* The last operand is immediate whenever we see just two
operands. */
opcodep[0] |= IMM_OFFSET_BIT;
/* Now, we could either have an implied "0" as the Z operand, or
it could be the constant of a "base address plus offset". It
depends on whether it is allowed; only memory operations, as
signified by instruction->type and "T" and "X" operand types,
and it depends on whether we find a register in the second
operand, exp[1]. */
if (exp[1].X_op == O_register && exp[1].X_add_number <= 255)
{
/* A zero then; all done. */
opcodep[2] = exp[1].X_add_number;
break;
}
/* Not known as a register. Is base address plus offset
allowed, or can we assume that it is a register anyway? */
if ((instruction->operands != mmix_operands_regs_z_opt
&& instruction->operands != mmix_operands_x_regs_z
&& instruction->operands != mmix_operands_pushgo)
|| (instruction->type != mmix_type_memaccess_octa
&& instruction->type != mmix_type_memaccess_tetra
&& instruction->type != mmix_type_memaccess_wyde
&& instruction->type != mmix_type_memaccess_byte
&& instruction->type != mmix_type_memaccess_block
&& instruction->type != mmix_type_jsr
&& instruction->type != mmix_type_branch))
{
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, exp + 1, 0, BFD_RELOC_MMIX_REG);
break;
}
/* To avoid getting a NULL add_symbol for constants and then
catching a SEGV in write_relocs since it doesn't handle
constants well for relocs other than PC-relative, we need to
pass expressions as symbols and use fix_new, not fix_new_exp. */
sym = make_expr_symbol (exp + 1);
/* Mark the symbol as being OK for a reloc. */
symbol_get_bfdsym (sym)->flags |= BSF_KEEP;
/* Now we know it can be a "base address plus offset". Add
proper fixup types so we can handle this later, when we've
parsed everything. */
tmpfixP
= fix_new (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, sym, 0, 0, BFD_RELOC_MMIX_BASE_PLUS_OFFSET);
/* This is a non-trivial fixup: the ->fx_offset will not
reflect the stored value, so the generic overflow test
doesn't apply. */
tmpfixP->fx_no_overflow = 1;
break;
}
if (exp[1].X_op == O_register)
opcodep[2] = exp[1].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, exp + 1, 0, BFD_RELOC_MMIX_REG);
/* In mmixal compatibility mode, we allow special registers as
constants for the Z operand. They have 256 added to their
register numbers, so the right thing will happen if we just treat
those as constants. */
if (exp[2].X_op == O_register && exp[2].X_add_number <= 255)
opcodep[3] = exp[2].X_add_number;
else if (exp[2].X_op == O_constant
|| (exp[2].X_op == O_register && exp[2].X_add_number > 255))
{
opcodep[3] = exp[2].X_add_number;
opcodep[0] |= IMM_OFFSET_BIT;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 2, 0,
(instruction->operands == mmix_operands_set
|| instruction->operands == mmix_operands_regs)
? BFD_RELOC_MMIX_REG : BFD_RELOC_MMIX_REG_OR_BYTE);
break;
case mmix_operands_pop:
/* POP, one eight and one 16-bit operand. */
if (n_operands == 0 && ! mmix_gnu_syntax)
break;
if (n_operands == 1 && ! mmix_gnu_syntax)
goto a_single_24_bit_number_operand;
/* FALLTHROUGH. */
case mmix_operands_reg_yz:
/* A register and a 16-bit unsigned number. */
if (n_operands != 2
|| exp[1].X_op == O_register
|| (exp[1].X_op == O_constant
&& (exp[1].X_add_number > 0xffff || exp[1].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[1].X_op == O_constant)
{
opcodep[2] = (exp[1].X_add_number >> 8) & 255;
opcodep[3] = exp[1].X_add_number & 255;
}
else
/* FIXME: This doesn't bring us unsignedness checking. */
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
2, exp + 1, 0, BFD_RELOC_16);
break;
case mmix_operands_jmp:
/* A JMP. Everything is already done. */
break;
case mmix_operands_roundregs:
/* Two registers with optional rounding mode or constant in between. */
if ((n_operands == 3 && exp[2].X_op == O_constant)
|| (n_operands == 2 && exp[1].X_op == O_constant))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
/* FALLTHROUGH. */
case mmix_operands_roundregs_z:
/* Like FLOT, "$X,ROUND_MODE,$Z|Z", but the rounding mode is
optional and can be the corresponding constant. */
{
/* Which exp index holds the second operand (not the rounding
mode). */
int op2no = n_operands - 1;
if ((n_operands != 2 && n_operands != 3)
|| ((exp[op2no].X_op == O_register
&& exp[op2no].X_add_number > 255)
|| (exp[op2no].X_op == O_constant
&& (exp[op2no].X_add_number > 255
|| exp[op2no].X_add_number < 0)))
|| (n_operands == 3
/* We don't allow for the rounding mode to be deferred; it
must be determined in the "first pass". It cannot be a
symbol equated to a rounding mode, but defined after
the first use. */
&& ((exp[1].X_op == O_register
&& exp[1].X_add_number < 512)
|| (exp[1].X_op == O_constant
&& (exp[1].X_add_number < 0
|| exp[1].X_add_number > 4))
|| (exp[1].X_op != O_register
&& exp[1].X_op != O_constant))))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
/* Add rounding mode if present. */
if (n_operands == 3)
opcodep[2] = exp[1].X_add_number & 255;
if (exp[op2no].X_op == O_register)
opcodep[3] = exp[op2no].X_add_number;
else if (exp[op2no].X_op == O_constant)
{
opcodep[3] = exp[op2no].X_add_number;
opcodep[0] |= IMM_OFFSET_BIT;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + op2no, 0,
instruction->operands == mmix_operands_roundregs
? BFD_RELOC_MMIX_REG
: BFD_RELOC_MMIX_REG_OR_BYTE);
break;
}
case mmix_operands_sync:
a_single_24_bit_number_operand:
if (n_operands != 1
|| exp[0].X_op == O_register
|| (exp[0].X_op == O_constant
&& (exp[0].X_add_number > 0xffffff || exp[0].X_add_number < 0)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[0].X_op == O_constant)
{
opcodep[1] = (exp[0].X_add_number >> 16) & 255;
opcodep[2] = (exp[0].X_add_number >> 8) & 255;
opcodep[3] = exp[0].X_add_number & 255;
}
else
/* FIXME: This doesn't bring us unsignedness checking. */
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
3, exp + 0, 0, BFD_RELOC_24);
break;
case mmix_operands_neg:
/* Operands "$X,Y,$Z|Z"; NEG or NEGU. Y is optional, 0 is default. */
if ((n_operands != 3 && n_operands != 2)
|| (n_operands == 3 && exp[1].X_op == O_register)
|| ((exp[1].X_op == O_constant || exp[1].X_op == O_register)
&& (exp[1].X_add_number > 255 || exp[1].X_add_number < 0))
|| (n_operands == 3
&& ((exp[2].X_op == O_register && exp[2].X_add_number > 255)
|| (exp[2].X_op == O_constant
&& (exp[2].X_add_number > 255
|| exp[2].X_add_number < 0)))))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (n_operands == 2)
{
if (exp[1].X_op == O_register)
opcodep[3] = exp[1].X_add_number;
else if (exp[1].X_op == O_constant)
{
opcodep[3] = exp[1].X_add_number;
opcodep[0] |= IMM_OFFSET_BIT;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 1, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
break;
}
if (exp[1].X_op == O_constant)
opcodep[2] = exp[1].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, exp + 1, 0, BFD_RELOC_8);
if (exp[2].X_op == O_register)
opcodep[3] = exp[2].X_add_number;
else if (exp[2].X_op == O_constant)
{
opcodep[3] = exp[2].X_add_number;
opcodep[0] |= IMM_OFFSET_BIT;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 2, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
break;
case mmix_operands_regaddr:
/* A GETA/branch-type. */
break;
case mmix_operands_get:
/* "$X,spec_reg"; GET.
Like with rounding modes, we demand that the special register or
symbol is already defined when we get here at the point of use. */
if (n_operands != 2
|| (exp[1].X_op == O_register
&& (exp[1].X_add_number < 256 || exp[1].X_add_number >= 512))
|| (exp[1].X_op == O_constant
&& (exp[1].X_add_number < 0 || exp[1].X_add_number > 256))
|| (exp[1].X_op != O_constant && exp[1].X_op != O_register))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
opcodep[3] = exp[1].X_add_number - 256;
break;
case mmix_operands_put:
/* "spec_reg,$Z|Z"; PUT. */
if (n_operands != 2
|| (exp[0].X_op == O_register
&& (exp[0].X_add_number < 256 || exp[0].X_add_number >= 512))
|| (exp[0].X_op == O_constant
&& (exp[0].X_add_number < 0 || exp[0].X_add_number > 256))
|| (exp[0].X_op != O_constant && exp[0].X_op != O_register))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
opcodep[1] = exp[0].X_add_number - 256;
/* Note that the Y field is zero. */
if (exp[1].X_op == O_register)
opcodep[3] = exp[1].X_add_number;
else if (exp[1].X_op == O_constant)
{
opcodep[3] = exp[1].X_add_number;
opcodep[0] |= IMM_OFFSET_BIT;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 1, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
break;
case mmix_operands_save:
/* "$X,0"; SAVE. */
if (n_operands != 2
|| exp[1].X_op != O_constant
|| exp[1].X_add_number != 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
break;
case mmix_operands_unsave:
if (n_operands < 2 && ! mmix_gnu_syntax)
{
if (n_operands == 1)
{
if (exp[0].X_op == O_register)
opcodep[3] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp, 0, BFD_RELOC_MMIX_REG);
}
break;
}
/* "0,$Z"; UNSAVE. */
if (n_operands != 2
|| exp[0].X_op != O_constant
|| exp[0].X_add_number != 0
|| exp[1].X_op == O_constant
|| (exp[1].X_op == O_register
&& exp[1].X_add_number > 255))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[1].X_op == O_register)
opcodep[3] = exp[1].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 1, 0, BFD_RELOC_MMIX_REG);
break;
case mmix_operands_xyz_opt:
/* SWYM, TRIP, TRAP: zero, one, two or three operands. It's
unspecified whether operands are registers or constants, but
when we find register syntax, we require operands to be literal and
within 0..255. */
if (n_operands == 0 && ! mmix_gnu_syntax)
/* Zeros are in place - nothing needs to be done for zero
operands. We don't allow this in GNU syntax mode, because it
was believed that the risk of missing to supply an operand is
higher than the benefit of not having to specify a zero. */
;
else if (n_operands == 1 && exp[0].X_op != O_register)
{
if (exp[0].X_op == O_constant)
{
if (exp[0].X_add_number > 255*256*256
|| exp[0].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
{
opcodep[1] = (exp[0].X_add_number >> 16) & 255;
opcodep[2] = (exp[0].X_add_number >> 8) & 255;
opcodep[3] = exp[0].X_add_number & 255;
}
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
3, exp, 0, BFD_RELOC_24);
}
else if (n_operands == 2
&& exp[0].X_op != O_register
&& exp[1].X_op != O_register)
{
/* Two operands. */
if (exp[0].X_op == O_constant)
{
if (exp[0].X_add_number > 255
|| exp[0].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
opcodep[1] = exp[0].X_add_number & 255;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp, 0, BFD_RELOC_8);
if (exp[1].X_op == O_constant)
{
if (exp[1].X_add_number > 255*256
|| exp[1].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
{
opcodep[2] = (exp[1].X_add_number >> 8) & 255;
opcodep[3] = exp[1].X_add_number & 255;
}
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
2, exp + 1, 0, BFD_RELOC_16);
}
else if (n_operands == 3
&& exp[0].X_op != O_register
&& exp[1].X_op != O_register
&& exp[2].X_op != O_register)
{
/* Three operands. */
if (exp[0].X_op == O_constant)
{
if (exp[0].X_add_number > 255
|| exp[0].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
opcodep[1] = exp[0].X_add_number & 255;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp, 0, BFD_RELOC_8);
if (exp[1].X_op == O_constant)
{
if (exp[1].X_add_number > 255
|| exp[1].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
opcodep[2] = exp[1].X_add_number & 255;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, exp + 1, 0, BFD_RELOC_8);
if (exp[2].X_op == O_constant)
{
if (exp[2].X_add_number > 255
|| exp[2].X_add_number < 0)
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
else
opcodep[3] = exp[2].X_add_number & 255;
}
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 2, 0, BFD_RELOC_8);
}
else
{
/* We can't get here for other cases. */
gas_assert (n_operands <= 3);
/* The meaning of operands to TRIP and TRAP is not defined (and
SWYM operands aren't enforced in mmixal, so let's avoid
that). We add combinations not handled above here as we find
them and as they're reported. */
if (n_operands == 3)
{
/* Don't require non-register operands. Always generate
fixups, so we don't have to copy lots of code and create
maintenance problems. TRIP is supposed to be a rare
instruction, so the overhead should not matter. We
aren't allowed to fix_new_exp for an expression which is
an O_register at this point, however.
Don't use BFD_RELOC_MMIX_REG_OR_BYTE as that modifies
the insn for a register in the Z field and we want
consistency. */
if (exp[0].X_op == O_register)
opcodep[1] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp, 0, BFD_RELOC_8);
if (exp[1].X_op == O_register)
opcodep[2] = exp[1].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
1, exp + 1, 0, BFD_RELOC_8);
if (exp[2].X_op == O_register)
opcodep[3] = exp[2].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 2, 0, BFD_RELOC_8);
}
else if (n_operands == 2)
{
if (exp[0].X_op == O_register)
opcodep[1] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
1, exp, 0, BFD_RELOC_8);
if (exp[1].X_op == O_register)
opcodep[3] = exp[1].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
2, exp + 1, 0, BFD_RELOC_16);
}
else
{
/* We can't get here for other cases. */
gas_assert (n_operands == 1 && exp[0].X_op == O_register);
opcodep[3] = exp[0].X_add_number;
}
}
break;
case mmix_operands_resume:
if (n_operands == 0 && ! mmix_gnu_syntax)
break;
if (n_operands != 1
|| exp[0].X_op == O_register
|| (exp[0].X_op == O_constant
&& (exp[0].X_add_number < 0
|| exp[0].X_add_number > 255)))
{
as_bad (_("invalid operands to opcode %s: `%s'"),
instruction->name, operands);
return;
}
if (exp[0].X_op == O_constant)
opcodep[3] = exp[0].X_add_number;
else
fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
1, exp + 0, 0, BFD_RELOC_8);
break;
case mmix_operands_pushj:
/* All is done for PUSHJ already. */
break;
default:
BAD_CASE (instruction->operands);
}
}
/* For the benefit of insns that start with a digit, we assemble by way of
tc_unrecognized_line too, through this function. */
int
mmix_assemble_return_nonzero (char *str)
{
int last_error_count = had_errors ();
char *s2 = str;
char c;
/* Normal instruction handling downcases, so we must too. */
while (ISALNUM (*s2))
{
if (ISUPPER ((unsigned char) *s2))
*s2 = TOLOWER (*s2);
s2++;
}
/* Cut the line for sake of the assembly. */
for (s2 = str; *s2 && *s2 != '\n'; s2++)
;
c = *s2;
*s2 = 0;
md_assemble (str);
*s2 = c;
return had_errors () == last_error_count;
}
/* The PREFIX pseudo. */
static void
s_prefix (int unused ATTRIBUTE_UNUSED)
{
char *p;
int c;
SKIP_WHITESPACE ();
c = get_symbol_name (&p);
/* Resetting prefix? */
if (*p == ':' && p[1] == 0)
mmix_current_prefix = NULL;
else
{
/* Put this prefix on the mmix symbols obstack. We could malloc and
free it separately, but then we'd have to worry about that.
People using up memory on prefixes have other problems. */
obstack_grow (&mmix_sym_obstack, p, strlen (p) + 1);
p = obstack_finish (&mmix_sym_obstack);
/* Accumulate prefixes, and strip a leading ':'. */
if (mmix_current_prefix != NULL || *p == ':')
p = mmix_prefix_name (p);
mmix_current_prefix = p;
}
(void) restore_line_pointer (c);
mmix_handle_rest_of_empty_line ();
}
/* We implement prefixes by using the tc_canonicalize_symbol_name hook,
and store each prefixed name on a (separate) obstack. This means that
the name is on the "notes" obstack in non-prefixed form and on the
mmix_sym_obstack in prefixed form, but currently it is not worth
rewriting the whole GAS symbol handling to improve "hooking" to avoid
that. (It might be worth a rewrite for other reasons, though). */
char *
mmix_prefix_name (char *shortname)
{
if (*shortname == ':')
return shortname + 1;
if (mmix_current_prefix == NULL)
as_fatal (_("internal: mmix_prefix_name but empty prefix"));
if (*shortname == '$')
return shortname;
obstack_grow (&mmix_sym_obstack, mmix_current_prefix,
strlen (mmix_current_prefix));
obstack_grow (&mmix_sym_obstack, shortname, strlen (shortname) + 1);
return obstack_finish (&mmix_sym_obstack);
}
/* The GREG pseudo. At LABEL, we have the name of a symbol that we
want to make a register symbol, and which should be initialized with
the value in the expression at INPUT_LINE_POINTER (defaulting to 0).
Either and (perhaps less meaningful) both may be missing. LABEL must
be persistent, perhaps allocated on an obstack. */
static void
mmix_greg_internal (char *label)
{
expressionS *expP = &mmix_raw_gregs[n_of_raw_gregs].exp;
segT section;
/* Don't set the section to register contents section before the
expression has been parsed; it may refer to the current position. */
section = expression (expP);
/* FIXME: Check that no expression refers to the register contents
section. May need to be done in elf64-mmix.c. */
if (expP->X_op == O_absent)
{
/* Default to zero if the expression was absent. */
expP->X_op = O_constant;
expP->X_add_number = 0;
expP->X_unsigned = 0;
expP->X_add_symbol = NULL;
expP->X_op_symbol = NULL;
}
if (section == undefined_section)
{
/* This is an error or a LOC with an expression involving
forward references. For the expression to be correctly
evaluated, we need to force a proper symbol; gas loses track
of the segment for "local symbols". */
if (expP->X_op == O_add)
{
symbol_get_value_expression (expP->X_op_symbol);
symbol_get_value_expression (expP->X_add_symbol);
}
else
{
gas_assert (expP->X_op == O_symbol);
symbol_get_value_expression (expP->X_add_symbol);
}
}
/* We must handle prefixes here, as we save the labels and expressions
to be output later. */
mmix_raw_gregs[n_of_raw_gregs].label
= mmix_current_prefix == NULL ? label : mmix_prefix_name (label);
if (n_of_raw_gregs == MAX_GREGS - 1)
as_bad (_("too many GREG registers allocated (max %d)"), MAX_GREGS);
else
n_of_raw_gregs++;
mmix_handle_rest_of_empty_line ();
}
/* The ".greg label,expr" worker. */
static void
s_greg (int unused ATTRIBUTE_UNUSED)
{
char *p;
char c;
/* This will skip over what can be a symbol and zero out the next
character, which we assume is a ',' or other meaningful delimiter.
What comes after that is the initializer expression for the
register. */
c = get_symbol_name (&p);
if (c == '"')
c = * ++ input_line_pointer;
if (! is_end_of_line[(unsigned char) c])
input_line_pointer++;
if (*p)
{
/* The label must be persistent; it's not used until after all input
has been seen. */
obstack_grow (&mmix_sym_obstack, p, strlen (p) + 1);
mmix_greg_internal (obstack_finish (&mmix_sym_obstack));
}
else
mmix_greg_internal (NULL);
}
/* The "BSPEC expr" worker. */
static void
s_bspec (int unused ATTRIBUTE_UNUSED)
{
asection *expsec;
asection *sec;
char secname[sizeof (MMIX_OTHER_SPEC_SECTION_PREFIX) + 20]
= MMIX_OTHER_SPEC_SECTION_PREFIX;
expressionS exp;
int n;
/* Get a constant expression which we can evaluate *now*. Supporting
more complex (though assembly-time computable) expressions is
feasible but Too Much Work for something of unknown usefulness like
BSPEC-ESPEC. */
expsec = expression (&exp);
mmix_handle_rest_of_empty_line ();
/* Check that we don't have another BSPEC in progress. */
if (doing_bspec)
{
as_bad (_("BSPEC already active. Nesting is not supported."));
return;
}
if (exp.X_op != O_constant
|| expsec != absolute_section
|| exp.X_add_number < 0
|| exp.X_add_number > 65535)
{
as_bad (_("invalid BSPEC expression"));
exp.X_add_number = 0;
}
n = (int) exp.X_add_number;
sprintf (secname + strlen (MMIX_OTHER_SPEC_SECTION_PREFIX), "%d", n);
sec = bfd_get_section_by_name (stdoutput, secname);
if (sec == NULL)
{
/* We need a non-volatile name as it will be stored in the section
struct. */
char *newsecname = xstrdup (secname);
sec = bfd_make_section (stdoutput, newsecname);
if (sec == NULL)
as_fatal (_("can't create section %s"), newsecname);
if (!bfd_set_section_flags (sec,
bfd_section_flags (sec) | SEC_READONLY))
as_fatal (_("can't set section flags for section %s"), newsecname);
}
/* Tell ELF about the pending section change. */
obj_elf_section_change_hook ();
subseg_set (sec, 0);
/* Save position for missing ESPEC. */
bspec_file = as_where (&bspec_line);
doing_bspec = 1;
}
/* The "ESPEC" worker. */
static void
s_espec (int unused ATTRIBUTE_UNUSED)
{
/* First, check that we *do* have a BSPEC in progress. */
if (! doing_bspec)
{
as_bad (_("ESPEC without preceding BSPEC"));
return;
}
mmix_handle_rest_of_empty_line ();
doing_bspec = 0;
/* When we told ELF about the section change in s_bspec, it stored the
previous section for us so we can get at it with the equivalent of a
.previous pseudo. */
obj_elf_previous (0);
}
/* The " .local expr" and " local expr" worker. We make a BFD_MMIX_LOCAL
relocation against the current position against the expression.
Implementing this by means of contents in a section lost. */
static void
mmix_s_local (int unused ATTRIBUTE_UNUSED)
{
expressionS exp;
/* Don't set the section to register contents section before the
expression has been parsed; it may refer to the current position in
some contorted way. */
expression (&exp);
if (exp.X_op == O_absent)
{
as_bad (_("missing local expression"));
return;
}
else if (exp.X_op == O_register)
{
/* fix_new_exp doesn't like O_register. Should be configurable.
We're fine with a constant here, though. */
exp.X_op = O_constant;
}
fix_new_exp (frag_now, 0, 0, &exp, 0, BFD_RELOC_MMIX_LOCAL);
mmix_handle_rest_of_empty_line ();
}
/* Set fragP->fr_var to the initial guess of the size of a relaxable insn
and return it. Sizes of other instructions are not known. This
function may be called multiple times. */
int
md_estimate_size_before_relax (fragS *fragP, segT segment)
{
int length;
#define HANDLE_RELAXABLE(state) \
case ENCODE_RELAX (state, STATE_UNDF): \
if (fragP->fr_symbol != NULL \
&& S_GET_SEGMENT (fragP->fr_symbol) == segment \
&& !S_IS_WEAK (fragP->fr_symbol)) \
{ \
/* The symbol lies in the same segment - a relaxable case. */ \
fragP->fr_subtype \
= ENCODE_RELAX (state, STATE_ZERO); \
} \
break;
switch (fragP->fr_subtype)
{
HANDLE_RELAXABLE (STATE_GETA);
HANDLE_RELAXABLE (STATE_BCC);
HANDLE_RELAXABLE (STATE_JMP);
case ENCODE_RELAX (STATE_PUSHJ, STATE_UNDF):
if (fragP->fr_symbol != NULL
&& S_GET_SEGMENT (fragP->fr_symbol) == segment
&& !S_IS_WEAK (fragP->fr_symbol))
/* The symbol lies in the same segment - a relaxable case. */
fragP->fr_subtype = ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO);
else if (pushj_stubs)
/* If we're to generate stubs, assume we can reach a stub after
the section. */
fragP->fr_subtype = ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO);
/* FALLTHROUGH. */
case ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO):
case ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO):
/* We need to distinguish different relaxation rounds. */
seg_info (segment)->tc_segment_info_data.last_stubfrag = fragP;
break;
case ENCODE_RELAX (STATE_GETA, STATE_ZERO):
case ENCODE_RELAX (STATE_BCC, STATE_ZERO):
case ENCODE_RELAX (STATE_JMP, STATE_ZERO):
/* When relaxing a section for the second time, we don't need to do
anything except making sure that fr_var is set right. */
break;
case STATE_GREG_DEF:
length = fragP->tc_frag_data != NULL ? 0 : 8;
fragP->fr_var = length;
/* Don't consult the relax_table; it isn't valid for this
relaxation. */
return length;
break;
default:
BAD_CASE (fragP->fr_subtype);
}
length = mmix_relax_table[fragP->fr_subtype].rlx_length;
fragP->fr_var = length;
return length;
}
/* 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. */
const char *
md_atof (int type, char *litP, int *sizeP)
{
if (type == 'r')
type = 'f';
/* FIXME: Having 'f' in FLT_CHARS (and here) makes it
problematic to also have a forward reference in an expression.
The testsuite wants it, and it's customary.
We'll deal with the real problems when they come; we share the
problem with most other ports. */
return ieee_md_atof (type, litP, sizeP, true);
}
/* Convert variable-sized frags into one or more fixups. */
void
md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT sec ATTRIBUTE_UNUSED,
fragS *fragP)
{
/* Pointer to first byte in variable-sized part of the frag. */
char *var_partp;
/* Pointer to first opcode byte in frag. */
char *opcodep;
/* Size in bytes of variable-sized part of frag. */
int var_part_size = 0;
/* This is part of *fragP. It contains all information about addresses
and offsets to varying parts. */
symbolS *symbolP;
unsigned long var_part_offset;
/* This is the frag for the opcode. It, rather than fragP, must be used
when emitting a frag for the opcode. */
fragS *opc_fragP = fragP->tc_frag_data;
fixS *tmpfixP;
/* Where, in file space, does addr point? */
bfd_vma target_address;
bfd_vma opcode_address;
know (fragP->fr_type == rs_machine_dependent);
var_part_offset = fragP->fr_fix;
var_partp = fragP->fr_literal + var_part_offset;
opcodep = fragP->fr_opcode;
symbolP = fragP->fr_symbol;
target_address
= ((symbolP ? S_GET_VALUE (symbolP) : 0) + fragP->fr_offset);
/* The opcode that would be extended is the last four "fixed" bytes. */
opcode_address = fragP->fr_address + fragP->fr_fix - 4;
switch (fragP->fr_subtype)
{
case ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO):
/* Setting the unknown bits to 0 seems the most appropriate. */
mmix_set_geta_branch_offset (opcodep, 0);
tmpfixP = fix_new (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
fragP->fr_symbol, fragP->fr_offset, 1,
BFD_RELOC_MMIX_PUSHJ_STUBBABLE);
COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
var_part_size = 0;
/* This is a non-trivial fixup; we'll be calling a generated
stub, whose address fits into the fixup. The actual target,
as reflected by the fixup value, is further away than fits
into the fixup, so the generic overflow test doesn't
apply. */
tmpfixP->fx_no_overflow = 1;
break;
case ENCODE_RELAX (STATE_GETA, STATE_ZERO):
case ENCODE_RELAX (STATE_BCC, STATE_ZERO):
case ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO):
mmix_set_geta_branch_offset (opcodep, target_address - opcode_address);
if (linkrelax)
{
tmpfixP
= fix_new (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
fragP->fr_symbol, fragP->fr_offset, 1,
BFD_RELOC_MMIX_ADDR19);
COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
}
var_part_size = 0;
break;
case ENCODE_RELAX (STATE_JMP, STATE_ZERO):
mmix_set_jmp_offset (opcodep, target_address - opcode_address);
if (linkrelax)
{
tmpfixP
= fix_new (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
fragP->fr_symbol, fragP->fr_offset, 1,
BFD_RELOC_MMIX_ADDR27);
COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
}
var_part_size = 0;
break;
case STATE_GREG_DEF:
if (fragP->tc_frag_data == NULL)
{
/* We must initialize data that's supposed to be "fixed up" to
avoid emitting garbage, because md_apply_fix won't do
anything for undefined symbols. */
md_number_to_chars (var_partp, 0, 8);
tmpfixP
= fix_new (fragP, var_partp - fragP->fr_literal, 8,
fragP->fr_symbol, fragP->fr_offset, 0, BFD_RELOC_64);
COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
mmix_gregs[n_of_cooked_gregs++] = tmpfixP;
var_part_size = 8;
}
else
var_part_size = 0;
break;
#define HANDLE_MAX_RELOC(state, reloc) \
case ENCODE_RELAX (state, STATE_MAX): \
var_part_size \
= mmix_relax_table[ENCODE_RELAX (state, STATE_MAX)].rlx_length; \
mmix_fill_nops (var_partp, var_part_size / 4); \
if (warn_on_expansion) \
as_warn_where (fragP->fr_file, fragP->fr_line, \
_("operand out of range, instruction expanded")); \
tmpfixP = fix_new (fragP, var_partp - fragP->fr_literal - 4, 8, \
fragP->fr_symbol, fragP->fr_offset, 1, reloc); \
COPY_FR_WHERE_TO_FX (fragP, tmpfixP); \
break
HANDLE_MAX_RELOC (STATE_GETA, BFD_RELOC_MMIX_GETA);
HANDLE_MAX_RELOC (STATE_BCC, BFD_RELOC_MMIX_CBRANCH);
HANDLE_MAX_RELOC (STATE_PUSHJ, BFD_RELOC_MMIX_PUSHJ);
HANDLE_MAX_RELOC (STATE_JMP, BFD_RELOC_MMIX_JMP);
default:
BAD_CASE (fragP->fr_subtype);
break;
}
fragP->fr_fix += var_part_size;
fragP->fr_var = 0;
}
/* Applies the desired value to the specified location.
Also sets up addends for RELA type relocations.
Stolen from tc-mcore.c.
Note that this function isn't called when linkrelax != 0. */
void
md_apply_fix (fixS *fixP, valueT *valP, segT segment)
{
char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;
/* Note: use offsetT because it is signed, valueT is unsigned. */
offsetT val = (offsetT) * valP;
segT symsec
= (fixP->fx_addsy == NULL
? absolute_section : S_GET_SEGMENT (fixP->fx_addsy));
/* If the fix is relative to a symbol which is not defined, or, (if
pcrel), not in the same segment as the fix, we cannot resolve it
here. */
if (fixP->fx_addsy != NULL
&& (! S_IS_DEFINED (fixP->fx_addsy)
|| S_IS_WEAK (fixP->fx_addsy)
|| (fixP->fx_pcrel && symsec != segment)
|| (! fixP->fx_pcrel
&& symsec != absolute_section
&& ((fixP->fx_r_type != BFD_RELOC_MMIX_REG
&& fixP->fx_r_type != BFD_RELOC_MMIX_REG_OR_BYTE)
|| symsec != reg_section))))
{
fixP->fx_done = 0;
return;
}
else if (fixP->fx_r_type == BFD_RELOC_MMIX_LOCAL
|| fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|| fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
{
/* These are never "fixed". */
fixP->fx_done = 0;
return;
}
else
/* We assume every other relocation is "fixed". */
fixP->fx_done = 1;
switch (fixP->fx_r_type)
{
case BFD_RELOC_64:
case BFD_RELOC_32:
case BFD_RELOC_24:
case BFD_RELOC_16:
case BFD_RELOC_8:
case BFD_RELOC_64_PCREL:
case BFD_RELOC_32_PCREL:
case BFD_RELOC_24_PCREL:
case BFD_RELOC_16_PCREL:
case BFD_RELOC_8_PCREL:
md_number_to_chars (buf, val, fixP->fx_size);
break;
case BFD_RELOC_MMIX_ADDR19:
if (expand_op)
{
/* This shouldn't happen. */
BAD_CASE (fixP->fx_r_type);
break;
}
/* FALLTHROUGH. */
case BFD_RELOC_MMIX_GETA:
case BFD_RELOC_MMIX_CBRANCH:
case BFD_RELOC_MMIX_PUSHJ:
case BFD_RELOC_MMIX_PUSHJ_STUBBABLE:
/* If this fixup is out of range, punt to the linker to emit an
error. This should only happen with -no-expand. */
if (val < -(((offsetT) 1 << 19)/2)
|| val >= ((offsetT) 1 << 19)/2 - 1
|| (val & 3) != 0)
{
if (warn_on_expansion)
as_warn_where (fixP->fx_file, fixP->fx_line,
_("operand out of range"));
fixP->fx_done = 0;
val = 0;
}
mmix_set_geta_branch_offset (buf, val);
break;
case BFD_RELOC_MMIX_ADDR27:
if (expand_op)
{
/* This shouldn't happen. */
BAD_CASE (fixP->fx_r_type);
break;
}
/* FALLTHROUGH. */
case BFD_RELOC_MMIX_JMP:
/* If this fixup is out of range, punt to the linker to emit an
error. This should only happen with -no-expand. */
if (val < -(((offsetT) 1 << 27)/2)
|| val >= ((offsetT) 1 << 27)/2 - 1
|| (val & 3) != 0)
{
if (warn_on_expansion)
as_warn_where (fixP->fx_file, fixP->fx_line,
_("operand out of range"));
fixP->fx_done = 0;
val = 0;
}
mmix_set_jmp_offset (buf, val);
break;
case BFD_RELOC_MMIX_REG_OR_BYTE:
if (fixP->fx_addsy != NULL
&& (S_GET_SEGMENT (fixP->fx_addsy) != reg_section
|| S_GET_VALUE (fixP->fx_addsy) > 255)
&& S_GET_SEGMENT (fixP->fx_addsy) != absolute_section)
{
as_bad_where (fixP->fx_file, fixP->fx_line,
_("invalid operands"));
/* We don't want this "symbol" appearing in output, because
that will fail. */
fixP->fx_done = 1;
}
buf[0] = val;
/* If this reloc is for a Z field, we need to adjust
the opcode if we got a constant here.
FIXME: Can we make this more robust? */
if ((fixP->fx_where & 3) == 3
&& (fixP->fx_addsy == NULL
|| S_GET_SEGMENT (fixP->fx_addsy) == absolute_section))
buf[-3] |= IMM_OFFSET_BIT;
break;
case BFD_RELOC_MMIX_REG:
if (fixP->fx_addsy == NULL
|| S_GET_SEGMENT (fixP->fx_addsy) != reg_section
|| S_GET_VALUE (fixP->fx_addsy) > 255)
{
as_bad_where (fixP->fx_file, fixP->fx_line,
_("invalid operands"));
fixP->fx_done = 1;
}
*buf = val;
break;
case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
/* These are never "fixed". */
fixP->fx_done = 0;
return;
case BFD_RELOC_MMIX_PUSHJ_1:
case BFD_RELOC_MMIX_PUSHJ_2:
case BFD_RELOC_MMIX_PUSHJ_3:
case BFD_RELOC_MMIX_CBRANCH_J:
case BFD_RELOC_MMIX_CBRANCH_1:
case BFD_RELOC_MMIX_CBRANCH_2:
case BFD_RELOC_MMIX_CBRANCH_3:
case BFD_RELOC_MMIX_GETA_1:
case BFD_RELOC_MMIX_GETA_2:
case BFD_RELOC_MMIX_GETA_3:
case BFD_RELOC_MMIX_JMP_1:
case BFD_RELOC_MMIX_JMP_2:
case BFD_RELOC_MMIX_JMP_3:
default:
BAD_CASE (fixP->fx_r_type);
break;
}
if (fixP->fx_done)
/* Make sure that for completed fixups we have the value around for
use by e.g. mmix_frob_file. */
fixP->fx_offset = val;
}
/* A bsearch function for looking up a value against offsets for GREG
definitions. */
static int
cmp_greg_val_greg_symbol_fixes (const void *p1, const void *p2)
{
offsetT val1 = *(offsetT *) p1;
offsetT val2 = ((struct mmix_symbol_greg_fixes *) p2)->offs;
if (val1 >= val2 && val1 < val2 + 255)
return 0;
if (val1 > val2)
return 1;
return -1;
}
/* Generate a machine-dependent relocation. */
arelent *
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixP)
{
bfd_signed_vma val
= fixP->fx_offset
+ (fixP->fx_addsy != NULL
&& !S_IS_WEAK (fixP->fx_addsy)
&& !S_IS_COMMON (fixP->fx_addsy)
? S_GET_VALUE (fixP->fx_addsy) : 0);
arelent *relP;
bfd_reloc_code_real_type code = BFD_RELOC_NONE;
char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;
symbolS *addsy = fixP->fx_addsy;
asection *addsec = addsy == NULL ? NULL : S_GET_SEGMENT (addsy);
asymbol *baddsy = addsy != NULL ? symbol_get_bfdsym (addsy) : NULL;
bfd_vma addend
= val - (baddsy == NULL || S_IS_COMMON (addsy) || S_IS_WEAK (addsy)
? 0 : bfd_asymbol_value (baddsy));
/* A single " LOCAL expression" in the wrong section will not work when
linking to MMO; relocations for zero-content sections are then
ignored. Normally, relocations would modify section contents, and
you'd never think or be able to do something like that. The
relocation resulting from a LOCAL directive doesn't have an obvious
and mandatory location. I can't figure out a way to do this better
than just helping the user around this limitation here; hopefully the
code using the local expression is around. Putting the LOCAL
semantics in a relocation still seems right; a section didn't do. */
if (bfd_section_size (section) == 0)
as_bad_where
(fixP->fx_file, fixP->fx_line,
fixP->fx_r_type == BFD_RELOC_MMIX_LOCAL
/* The BFD_RELOC_MMIX_LOCAL-specific message is supposed to be
user-friendly, though a little bit non-substantial. */
? _("directive LOCAL must be placed in code or data")
: _("internal confusion: relocation in a section without contents"));
/* FIXME: Range tests for all these. */
switch (fixP->fx_r_type)
{
case BFD_RELOC_64:
case BFD_RELOC_32:
case BFD_RELOC_24:
case BFD_RELOC_16:
case BFD_RELOC_8:
code = fixP->fx_r_type;
if (addsy == NULL || bfd_is_abs_section (addsec))
{
/* Resolve this reloc now, as md_apply_fix would have done (not
called if -linkrelax). There is no point in keeping a reloc
to an absolute symbol. No reloc that is subject to
relaxation must be to an absolute symbol; difference
involving symbols in a specific section must be signalled as
an error if the relaxing cannot be expressed; having a reloc
to the resolved (now absolute) value does not help. */
md_number_to_chars (buf, val, fixP->fx_size);
return NULL;
}
break;
case BFD_RELOC_64_PCREL:
case BFD_RELOC_32_PCREL:
case BFD_RELOC_24_PCREL:
case BFD_RELOC_16_PCREL:
case BFD_RELOC_8_PCREL:
case BFD_RELOC_MMIX_LOCAL:
case BFD_RELOC_VTABLE_INHERIT:
case BFD_RELOC_VTABLE_ENTRY:
case BFD_RELOC_MMIX_GETA:
case BFD_RELOC_MMIX_GETA_1:
case BFD_RELOC_MMIX_GETA_2:
case BFD_RELOC_MMIX_GETA_3:
case BFD_RELOC_MMIX_CBRANCH:
case BFD_RELOC_MMIX_CBRANCH_J:
case BFD_RELOC_MMIX_CBRANCH_1:
case BFD_RELOC_MMIX_CBRANCH_2:
case BFD_RELOC_MMIX_CBRANCH_3:
case BFD_RELOC_MMIX_PUSHJ:
case BFD_RELOC_MMIX_PUSHJ_1:
case BFD_RELOC_MMIX_PUSHJ_2:
case BFD_RELOC_MMIX_PUSHJ_3:
case BFD_RELOC_MMIX_PUSHJ_STUBBABLE:
case BFD_RELOC_MMIX_JMP:
case BFD_RELOC_MMIX_JMP_1:
case BFD_RELOC_MMIX_JMP_2:
case BFD_RELOC_MMIX_JMP_3:
case BFD_RELOC_MMIX_ADDR19:
case BFD_RELOC_MMIX_ADDR27:
code = fixP->fx_r_type;
break;
case BFD_RELOC_MMIX_REG_OR_BYTE:
/* If we have this kind of relocation to an unknown symbol or to the
register contents section (that is, to a register), then we can't
resolve the relocation here. */
if (addsy != NULL
&& (bfd_is_und_section (addsec)
|| strcmp (bfd_section_name (addsec),
MMIX_REG_CONTENTS_SECTION_NAME) == 0))
{
code = fixP->fx_r_type;
break;
}
/* If the relocation is not to the register section or to the
absolute section (a numeric value), then we have an error. */
if (addsy != NULL
&& (S_GET_SEGMENT (addsy) != real_reg_section
|| val > 255
|| val < 0)
&& ! bfd_is_abs_section (addsec))
goto badop;
/* Set the "immediate" bit of the insn if this relocation is to Z
field when the value is a numeric value, i.e. not a register. */
if ((fixP->fx_where & 3) == 3
&& (addsy == NULL || bfd_is_abs_section (addsec)))
buf[-3] |= IMM_OFFSET_BIT;
buf[0] = val;
return NULL;
case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
if (addsy != NULL
&& strcmp (bfd_section_name (addsec),
MMIX_REG_CONTENTS_SECTION_NAME) == 0)
{
/* This changed into a register; the relocation is for the
register-contents section. The constant part remains zero. */
code = BFD_RELOC_MMIX_REG;
break;
}
/* If we've found out that this was indeed a register, then replace
with the register number. The constant part is already zero.
If we encounter any other defined symbol, then we must find a
suitable register and emit a reloc. */
if (addsy == NULL || addsec != real_reg_section)
{
struct mmix_symbol_gregs *gregs;
struct mmix_symbol_greg_fixes *fix;
if (S_IS_DEFINED (addsy)
&& !bfd_is_com_section (addsec)
&& !S_IS_WEAK (addsy))
{
if (! symbol_section_p (addsy) && ! bfd_is_abs_section (addsec))
as_fatal (_("internal: BFD_RELOC_MMIX_BASE_PLUS_OFFSET not resolved to section"));
/* If this is an absolute symbol sufficiently near
lowest_data_loc, then we canonicalize on the data
section. Note that val is signed here; we may subtract
lowest_data_loc which is unsigned. Careful with those
comparisons. */
if (lowest_data_loc != (bfd_vma) -1
&& (bfd_vma) val + 256 > lowest_data_loc
&& bfd_is_abs_section (addsec))
{
val -= (offsetT) lowest_data_loc;
addsy = section_symbol (data_section);
}
/* Likewise text section. */
else if (lowest_text_loc != (bfd_vma) -1
&& (bfd_vma) val + 256 > lowest_text_loc
&& bfd_is_abs_section (addsec))
{
val -= (offsetT) lowest_text_loc;
addsy = section_symbol (text_section);
}
}
gregs = *symbol_get_tc (addsy);
/* If that symbol does not have any associated GREG definitions,
we can't do anything. */
if (gregs == NULL
|| (fix = bsearch (&val, gregs->greg_fixes, gregs->n_gregs,
sizeof (gregs->greg_fixes[0]),
cmp_greg_val_greg_symbol_fixes)) == NULL
/* The register must not point *after* the address we want. */
|| fix->offs > val
/* Neither must the register point more than 255 bytes
before the address we want. */
|| fix->offs + 255 < val)
{
/* We can either let the linker allocate GREGs
automatically, or emit an error. */
if (allocate_undefined_gregs_in_linker)
{
/* The values in baddsy and addend are right. */
code = fixP->fx_r_type;
break;
}
else
as_bad_where (fixP->fx_file, fixP->fx_line,
_("no suitable GREG definition for operands"));
return NULL;
}
else
{
/* Transform the base-plus-offset reloc for the actual area
to a reloc for the register with the address of the area.
Put addend for register in Z operand. */
buf[1] = val - fix->offs;
code = BFD_RELOC_MMIX_REG;
baddsy
= (bfd_get_section_by_name (stdoutput,
MMIX_REG_CONTENTS_SECTION_NAME)
->symbol);
addend = fix->fix->fx_frag->fr_address + fix->fix->fx_where;
}
}
else if (S_GET_VALUE (addsy) > 255)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("invalid operands"));
else
{
*buf = val;
return NULL;
}
break;
case BFD_RELOC_MMIX_REG:
if (addsy != NULL
&& (bfd_is_und_section (addsec)
|| strcmp (bfd_section_name (addsec),
MMIX_REG_CONTENTS_SECTION_NAME) == 0))
{
code = fixP->fx_r_type;
break;
}
if (addsy != NULL
&& (addsec != real_reg_section
|| val > 255
|| val < 0)
&& ! bfd_is_und_section (addsec))
/* Drop through to error message. */
;
else
{
buf[0] = val;
return NULL;
}
/* FALLTHROUGH. */
/* The others are supposed to be handled by md_apply_fix.
FIXME: ... which isn't called when -linkrelax. Move over
md_apply_fix code here for everything reasonable. */
badop:
default:
as_bad_where
(fixP->fx_file, fixP->fx_line,
_("operands were not reducible at assembly-time"));
/* Unmark this symbol as used in a reloc, so we don't bump into a BFD
assert when trying to output reg_section. FIXME: A gas bug. */
fixP->fx_addsy = NULL;
return NULL;
}
relP = XNEW (arelent);
gas_assert (relP != 0);
relP->sym_ptr_ptr = XNEW (asymbol *);
*relP->sym_ptr_ptr = baddsy;
relP->address = fixP->fx_frag->fr_address + fixP->fx_where;
relP->addend = addend;
/* If this had been a.out, we would have had a kludge for weak symbols
here. */
relP->howto = bfd_reloc_type_lookup (stdoutput, code);
if (! relP->howto)
{
const char *name;
name = S_GET_NAME (addsy);
if (name == NULL)
name = _("<unknown>");
as_fatal (_("cannot generate relocation type for symbol %s, code %s"),
name, bfd_get_reloc_code_name (code));
}
return relP;
}
/* Do some reformatting of a line. FIXME: We could transform a mmixal
line into traditional (GNU?) format, unless #NO_APP, and get rid of all
ugly labels_without_colons etc. */
void
mmix_handle_mmixal (void)
{
char *insn;
char *s = input_line_pointer;
char *label = NULL;
char c;
if (pending_label != NULL)
as_fatal (_("internal: unhandled label %s"), pending_label);
if (mmix_gnu_syntax)
return;
/* If we're on a line with a label, check if it's a mmixal fb-label.
Save an indicator and skip the label; it must be set only after all
fb-labels of expressions are evaluated. */
if (ISDIGIT (s[0]) && s[1] == 'H' && ISSPACE (s[2]))
{
current_fb_label = s[0] - '0';
/* We have to skip the label, but also preserve the newlineness of
the previous character, since the caller checks that. It's a
mess we blame on the caller. */
s[1] = s[-1];
s += 2;
input_line_pointer = s;
while (*s && ISSPACE (*s) && ! is_end_of_line[(unsigned int) *s])
s++;
/* For errors emitted here, the book-keeping is off by one; the
caller is about to bump the counters. Adjust the error messages. */
if (is_end_of_line[(unsigned int) *s])
{
unsigned int line;
const char * name = as_where (&line);
as_bad_where (name, line + 1,
_("[0-9]H labels may not appear alone on a line"));
current_fb_label = -1;
}
if (*s == '.')
{
unsigned int line;
const char * name = as_where (&line);
as_bad_where (name, line + 1,
_("[0-9]H labels do not mix with dot-pseudos"));
current_fb_label = -1;
}
/* Back off to the last space before the opcode so we don't handle
the opcode as a label. */
s--;
}
else
current_fb_label = -1;
if (*s == '.')
{
/* If the first character is a '.', then it's a pseudodirective, not a
label. Make GAS not handle label-without-colon on this line. We
also don't do mmixal-specific stuff on this line. */
label_without_colon_this_line = 0;
return;
}
if (*s == 0 || is_end_of_line[(unsigned int) *s])
/* We avoid handling empty lines here. */
return;
if (is_name_beginner (*s))
label = s;
/* If there is a label, skip over it. */
while (*s && is_part_of_name (*s))
s++;
/* Find the start of the instruction or pseudo following the label,
if there is one. */
for (insn = s;
*insn && ISSPACE (*insn) && ! is_end_of_line[(unsigned int) *insn];
insn++)
/* Empty */
;
/* Remove a trailing ":" off labels, as they'd otherwise be considered
part of the name. But don't do this for local labels. */
if (s != input_line_pointer && s[-1] == ':'
&& (s - 2 != input_line_pointer
|| ! ISDIGIT (s[-2])))
s[-1] = ' ';
else if (label != NULL
/* For a lone label on a line, we don't attach it to the next
instruction or MMIXAL-pseudo (getting its alignment). Thus
is acts like a "normal" :-ended label. Ditto if it's
followed by a non-MMIXAL pseudo. */
&& !is_end_of_line[(unsigned int) *insn]
&& *insn != '.')
{
/* For labels that don't end in ":", we save it so we can later give
it the same alignment and address as the associated instruction. */
/* Make room for the label including the ending nul. */
size_t len_0 = s - label + 1;
/* Save this label on the MMIX symbol obstack. Saving it on an
obstack is needless for "IS"-pseudos, but it's harmless and we
avoid a little code-cluttering. */
obstack_grow (&mmix_sym_obstack, label, len_0);
pending_label = obstack_finish (&mmix_sym_obstack);
pending_label[len_0 - 1] = 0;
}
/* If we have a non-MMIXAL pseudo, we have not business with the rest of
the line. */
if (*insn == '.')
return;
/* Find local labels of operands. Look for "[0-9][FB]" where the
characters before and after are not part of words. Break if a single
or double quote is seen anywhere. It means we can't have local
labels as part of list with mixed quoted and unquoted members for
mmixal compatibility but we can't have it all. For the moment.
Replace the '<N>B' or '<N>F' with MAGIC_FB_BACKWARD_CHAR<N> and
MAGIC_FB_FORWARD_CHAR<N> respectively. */
/* First make sure we don't have any of the magic characters on the line
appearing as input. */
while (*s)
{
c = *s++;
if (is_end_of_line[(unsigned int) c])
break;
if (c == MAGIC_FB_BACKWARD_CHAR || c == MAGIC_FB_FORWARD_CHAR)
as_bad (_("invalid characters in input"));
}
/* Scan again, this time looking for ';' after operands. */
s = insn;
/* Skip the insn. */
while (*s
&& ! ISSPACE (*s)
&& *s != ';'
&& ! is_end_of_line[(unsigned int) *s])
s++;
/* Skip the spaces after the insn. */
while (*s
&& ISSPACE (*s)
&& *s != ';'
&& ! is_end_of_line[(unsigned int) *s])
s++;
/* Skip the operands. While doing this, replace [0-9][BF] with
(MAGIC_FB_BACKWARD_CHAR|MAGIC_FB_FORWARD_CHAR)[0-9]. */
while ((c = *s) != 0
&& ! ISSPACE (c)
&& c != ';'
&& ! is_end_of_line[(unsigned int) c])
{
if (c == '"')
{
s++;
/* FIXME: Test-case for semi-colon in string. */
while (*s
&& *s != '"'
&& (! is_end_of_line[(unsigned int) *s] || *s == ';'))
s++;
if (*s == '"')
s++;
}
else if (ISDIGIT (c))
{
if ((s[1] != 'B' && s[1] != 'F')
|| is_part_of_name (s[-1])
|| is_part_of_name (s[2])
/* Don't treat e.g. #1F as a local-label reference. */
|| (s != input_line_pointer && s[-1] == '#'))
s++;
else
{
s[0] = (s[1] == 'B'
? MAGIC_FB_BACKWARD_CHAR : MAGIC_FB_FORWARD_CHAR);
s[1] = c;
}
}
else
s++;
}
/* Skip any spaces after the operands. */
while (*s
&& ISSPACE (*s)
&& *s != ';'
&& !is_end_of_line[(unsigned int) *s])
s++;
/* If we're now looking at a semi-colon, then it's an end-of-line
delimiter. */
mmix_next_semicolon_is_eoln = (*s == ';');
/* Make IS into an EQU by replacing it with "= ". Only match upper-case
though; let lower-case be a syntax error. */
s = insn;
if (s[0] == 'I' && s[1] == 'S' && ISSPACE (s[2]))
{
*s = '=';
s[1] = ' ';
/* Since labels can start without ":", we have to handle "X IS 42"
in full here, or "X" will be parsed as a label to be set at ".". */
input_line_pointer = s;
/* Right after this function ends, line numbers will be bumped if
input_line_pointer[-1] = '\n'. We want accurate line numbers for
the equals call, so we bump them before the call, and make sure