This is the mail archive of the binutils@sources.redhat.com mailing list for the binutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

MMIX port, part 1/10: gas port.


I think it's time to submit the MMIX binutils port (mmix-knuth-mmixware).
MMIX is Don Knuth's educational processor, used in new editions of TAOCP.
Information about MMIX is also at
<URL:http://www-cs-faculty.stanford.edu/~knuth/mmix.html>.

While I wrote it to primarily serve the GCC port (which it does fine;
working GCC port hopefully soon to be submitted), it is to a large extent
compatible with mmixal, the combined assembler and linker, presented in
documents and tarballs located at
<URL:http://www-cs-faculty.stanford.edu/~knuth/mmix-news.html>.  The
binutils port handles 64-bit ELF and Knuth's mmo object format.  The
default is to use mmo for linked files, with ELF the only possible choice
for intermediate object files.  This works remarkably good; I thought I'd
have to link to ELF and objcopy to mmo to get at the ELF goodies.  Further
information about the implementation is in the gas, bfd and ld
documentation.

Ok to commit?

Part 1/10, gas port (except documentation and testsuite):

	* configure.in: Update for MMIX port.
	* Makefile.am: Ditto.  Regenerate dependencies.
	* Makefile.in: Regenerate.
	* configure: Regenerate.
	* config/tc-mmix.h, config/tc-mmix.c: New files.

Index: Makefile.am
===================================================================
RCS file: /cvs/src/src/gas/Makefile.am,v
retrieving revision 1.41
diff -p -c -r1.41 Makefile.am
*** Makefile.am	2001/09/30 15:55:46	1.41
--- Makefile.am	2001/09/30 16:27:53
*************** CPU_TYPES = \
*** 60,65 ****
--- 60,66 ----
  	m88k \
  	mcore \
  	mips \
+ 	mmix \
  	mn10200 \
  	mn10300 \
  	ns32k \
*************** CPU_OBJ_VALID = \
*** 112,118 ****
  	  esac ;; \
  	coff) valid=yes; \
  	  case $$c in \
! 	  cris | i860) \
  	    valid= ;; \
  	  esac ;; \
  	ecoff) \
--- 113,119 ----
  	  esac ;; \
  	coff) valid=yes; \
  	  case $$c in \
! 	  cris | i860 | mmix) \
  	    valid= ;; \
  	  esac ;; \
  	ecoff) \
*************** TARGET_CPU_CFILES = \
*** 244,249 ****
--- 245,251 ----
  	config/tc-m88k.c \
  	config/tc-mcore.c \
  	config/tc-mips.c \
+ 	config/tc-mmix.c \
  	config/tc-mn10200.c \
  	config/tc-mn10300.c \
  	config/tc-ns32k.c \
*************** TARGET_CPU_HFILES = \
*** 287,292 ****
--- 289,295 ----
  	config/tc-m88k.h \
  	config/tc-mcore.h \
  	config/tc-mips.h \
+ 	config/tc-mmix.h \
  	config/tc-mn10200.h \
  	config/tc-mn10300.h \
  	config/tc-ns32k.h \
*************** DEPTC_mips_elf = $(srcdir)/config/obj-el
*** 1186,1191 ****
--- 1189,1199 ----
    $(INCDIR)/obstack.h $(INCDIR)/safe-ctype.h $(INCDIR)/opcode/mips.h \
    itbl-ops.h $(INCDIR)/elf/mips.h $(INCDIR)/elf/reloc-macros.h \
    ecoff.h $(INCDIR)/coff/sym.h $(INCDIR)/coff/ecoff.h
+ DEPTC_mmix_elf = $(srcdir)/config/obj-elf.h $(BFDDIR)/elf-bfd.h \
+   $(INCDIR)/elf/common.h $(INCDIR)/elf/internal.h $(INCDIR)/elf/external.h \
+   $(INCDIR)/bfdlink.h $(srcdir)/config/tc-mmix.h subsegs.h \
+   $(INCDIR)/obstack.h $(INCDIR)/elf/mmix.h $(INCDIR)/elf/reloc-macros.h \
+   $(INCDIR)/opcode/mmix.h $(INCDIR)/safe-ctype.h dwarf2dbg.h
  DEPTC_mn10200_coff = $(srcdir)/config/obj-coff.h $(srcdir)/config/tc-mn10200.h \
    $(INCDIR)/coff/internal.h $(BFDDIR)/libcoff.h $(INCDIR)/bfdlink.h \
    $(INCDIR)/safe-ctype.h subsegs.h $(INCDIR)/obstack.h \
*************** DEPOBJ_mips_elf = $(srcdir)/config/obj-e
*** 1577,1582 ****
--- 1585,1594 ----
    subsegs.h $(INCDIR)/obstack.h ecoff.h $(INCDIR)/coff/sym.h \
    $(INCDIR)/coff/ecoff.h $(INCDIR)/elf/mips.h $(INCDIR)/elf/reloc-macros.h \
    $(INCDIR)/aout/aout64.h
+ DEPOBJ_mmix_elf = $(srcdir)/config/obj-elf.h $(BFDDIR)/elf-bfd.h \
+   $(INCDIR)/elf/common.h $(INCDIR)/elf/internal.h $(INCDIR)/elf/external.h \
+   $(INCDIR)/bfdlink.h $(srcdir)/config/tc-mmix.h $(INCDIR)/safe-ctype.h \
+   subsegs.h $(INCDIR)/obstack.h $(INCDIR)/aout/aout64.h
  DEPOBJ_mn10200_coff = $(srcdir)/config/obj-coff.h $(srcdir)/config/tc-mn10200.h \
    $(INCDIR)/coff/internal.h $(BFDDIR)/libcoff.h $(INCDIR)/bfdlink.h \
    $(INCDIR)/obstack.h subsegs.h
*************** DEP_mips_ecoff = $(srcdir)/config/obj-ec
*** 1885,1890 ****
--- 1897,1905 ----
  DEP_mips_elf = $(srcdir)/config/obj-elf.h $(BFDDIR)/elf-bfd.h \
    $(INCDIR)/elf/common.h $(INCDIR)/elf/internal.h $(INCDIR)/elf/external.h \
    $(INCDIR)/bfdlink.h $(srcdir)/config/tc-mips.h
+ DEP_mmix_elf = $(srcdir)/config/obj-elf.h $(BFDDIR)/elf-bfd.h \
+   $(INCDIR)/elf/common.h $(INCDIR)/elf/internal.h $(INCDIR)/elf/external.h \
+   $(INCDIR)/bfdlink.h $(srcdir)/config/tc-mmix.h
  DEP_mn10200_coff = $(srcdir)/config/obj-coff.h $(srcdir)/config/tc-mn10200.h \
    $(INCDIR)/coff/internal.h $(BFDDIR)/libcoff.h $(INCDIR)/bfdlink.h
  DEP_mn10200_elf = $(srcdir)/config/obj-elf.h $(BFDDIR)/elf-bfd.h \
Index: configure.in
===================================================================
RCS file: /cvs/src/src/gas/configure.in,v
retrieving revision 1.78
diff -p -c -r1.78 configure.in
*** configure.in	2001/09/14 11:46:40	1.78
--- configure.in	2001/09/30 16:28:05
*************** changequote([,])dnl
*** 364,369 ****
--- 364,370 ----
  			    AC_DEFINE(MIPS_STABS_ELF, 1,
  				[Use ELF stabs for MIPS, not ECOFF stabs])
  			    ;;
+       mmix-*-*)		    fmt=elf bfd_gas=yes ;;
        mn10200-*-*)	    fmt=elf bfd_gas=yes ;;
        mn10300-*-*)	    fmt=elf bfd_gas=yes ;;
        openrisc-*-*)	    fmt=elf bfd_gas=yes ;;
*** /dev/null	Tue Jan  1 05:00:00 1980
--- config/tc-mmix.h	Sun Sep 30 18:35:49 2001
***************
*** 0 ****
--- 1,214 ----
+ /* tc-mmix.h -- Header file for tc-mmix.c.
+    Copyright (C) 2001 Free Software Foundation, Inc.
+    Written by Hans-Peter Nilsson (hp@bitrange.com).
+
+    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 2, 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, 59 Temple Place - Suite 330, Boston, MA
+    02111-1307, USA. */
+
+ #define TC_MMIX
+
+ /* See gas/doc/internals.texi for explanation of these macros.  */
+ #define TARGET_FORMAT "elf64-mmix"
+ #define TARGET_ARCH bfd_arch_mmix
+ #define TARGET_BYTES_BIG_ENDIAN 1
+
+ extern const char mmix_comment_chars[];
+ #define tc_comment_chars mmix_comment_chars
+
+ extern const char mmix_symbol_chars[];
+ #define tc_symbol_chars mmix_symbol_chars
+
+ extern const char mmix_exp_chars[];
+ #define EXP_CHARS mmix_exp_chars
+
+ extern const char mmix_flt_chars[];
+ #define FLT_CHARS mmix_flt_chars
+
+ /* "@" is a synonym for ".".  */
+ #define LEX_AT (LEX_BEGIN_NAME)
+
+ extern int mmix_label_without_colon_this_line PARAMS ((void));
+ #define LABELS_WITHOUT_COLONS mmix_label_without_colon_this_line ()
+
+ extern int mmix_next_semicolon_is_eoln;
+ #define TC_EOL_IN_INSN(p) (*(p) == ';' && ! mmix_next_semicolon_is_eoln)
+
+ /* This is one direction we can get mmixal compatibility.  */
+ extern void mmix_handle_mmixal PARAMS ((void));
+ #define md_start_line_hook mmix_handle_mmixal
+
+ extern void mmix_md_begin PARAMS ((void));
+ #define md_begin mmix_md_begin
+
+ extern void mmix_md_end PARAMS ((void));
+ #define md_end mmix_md_end
+
+ extern int mmix_current_location \
+   PARAMS ((void (*fn) (expressionS *), expressionS *));
+ extern int mmix_parse_predefined_name PARAMS ((char *, expressionS *));
+
+ extern char *mmix_current_prefix;
+
+ /* A bit ugly, since we "know" that there's a static function
+    current_location that does what we want.  We also strip off a leading
+    ':' in another ugly way.
+
+    The [DVWIOUZX]_Handler symbols are provided when-used.  */
+
+ extern int mmix_gnu_syntax;
+ #define md_parse_name(name, exp, cpos)				\
+  (! mmix_gnu_syntax						\
+   && (name[0] == '@'						\
+       ? (! is_part_of_name (name[1])				\
+ 	 && mmix_current_location (current_location, exp))	\
+       : ((name[0] == ':' || isupper (name[0]))			\
+ 	 && mmix_parse_predefined_name (name, exp))))
+
+ extern char *mmix_prefix_name PARAMS ((char *));
+
+ /* We implement when *creating* a symbol, we also need to strip a ':' or
+    prepend a prefix.  */
+ #define tc_canonicalize_symbol_name(x) \
+  (mmix_current_prefix == NULL && (x)[0] != ':' ? (x) : mmix_prefix_name (x))
+
+ #define md_undefined_symbol(x) NULL
+
+ extern void mmix_fb_label PARAMS ((expressionS *));
+
+ /* Since integer_constant is local to expr.c, we have to make this a
+    macro.  FIXME: Do it cleaner.  */
+ #define md_operand(exp)							\
+  do {									\
+   if (input_line_pointer[0] == '#')					\
+     {									\
+       input_line_pointer++;						\
+       integer_constant (16, (exp));					\
+     }									\
+   else if (input_line_pointer[0] == '&'					\
+ 	   && input_line_pointer[1] != '&')				\
+     as_bad (_("`&' serial number operator is not supported"));	\
+   else									\
+     mmix_fb_label (exp);						\
+  } while (0)
+
+
+ /* Gas dislikes the 2ADD, 8ADD etc. insns, so we have to assemble them in
+    the error-recovery loop.  Hopefully there are no significant
+    differences.  Also, space on a line isn't gracefully handled.  */
+ extern int mmix_assemble_return_nonzero PARAMS ((char  *));
+ #define tc_unrecognized_line(c)						\
+  ((c) == ' '								\
+   || (((c) == '1' || (c) == '2' || (c) == '4' || (c) == '8')		\
+       && mmix_assemble_return_nonzero (input_line_pointer - 1)))
+
+ #define md_number_to_chars number_to_chars_bigendian
+
+ #define WORKING_DOT_WORD
+
+ extern const struct relax_type mmix_relax_table[];
+ #define TC_GENERIC_RELAX_TABLE mmix_relax_table
+
+ /* We use the relax table for everything except the GREG frags.  */
+ extern long mmix_md_relax_frag PARAMS ((segT, fragS *, long));
+ #define md_relax_frag mmix_md_relax_frag
+
+ #define tc_fix_adjustable(X)					\
+  ((! (X)->fx_addsy						\
+    || (! S_IS_WEAK ((X)->fx_addsy)				\
+        && S_GET_SEGMENT ((X)->fx_addsy) != reg_section))	\
+   && (X)->fx_r_type != BFD_RELOC_VTABLE_INHERIT			\
+   && (X)->fx_r_type != BFD_RELOC_VTABLE_ENTRY)
+
+ /* Adjust symbols which are registers.  */
+ #define tc_adjust_symtab() mmix_adjust_symtab ()
+ extern void mmix_adjust_symtab PARAMS ((void));
+
+ /* Avoid outputting GAS register section symbols.  This happens when the
+    assembly had errors, and will propagate to an assert in BFD.  FIXME:
+    It seems the symbol output when-errors is a bug in GAS.  Fix that
+    some time.  See also tc_gen_reloc.
+
+    Here's where we make all symbols global, when so requested.
+    We must avoid doing that for expression symbols or section symbols,
+    though.  */
+ extern int mmix_globalize_symbols;
+ #define tc_frob_symbol(sym, punt)			\
+  do {							\
+   if (S_GET_SEGMENT (sym) == reg_section		\
+       || (symp) == section_symbol (absolute_section))	\
+     (punt) = 1;						\
+ 							\
+   if (mmix_globalize_symbols				\
+       && ! symbol_section_p (sym)			\
+       && symp != section_symbol (absolute_section)	\
+       && (! S_IS_LOCAL (sym)				\
+ 	  || S_GET_SEGMENT (sym) == reg_section)	\
+       && (S_GET_SEGMENT (sym) != reg_section		\
+ 	  || (S_GET_NAME (sym)[0] != '$'		\
+ 	      && S_GET_VALUE (sym) < 256)))		\
+     S_SET_EXTERNAL (sym);				\
+  } while (0)
+
+ /* When relaxing, we need to emit various relocs we otherwise wouldn't.  */
+ #define TC_FORCE_RELOCATION(fix) mmix_force_relocation (fix)
+ extern int mmix_force_relocation PARAMS ((struct fix *));
+
+ /* Call md_pcrel_from_section(), not md_pcrel_from().  */
+ #define MD_PCREL_FROM_SECTION(FIXP, SEC) md_pcrel_from_section (FIXP, SEC)
+ extern long md_pcrel_from_section PARAMS ((struct fix *, segT));
+
+ #define MD_APPLY_FIX3
+
+ #define TC_HANDLES_FX_DONE
+
+ #define md_section_align(seg, size) (size)
+
+ #define LISTING_HEADER "GAS for MMIX"
+
+ /* The default of 4 means Bcc expansion looks like it's missing a line.  */
+ #define LISTING_LHS_CONT_LINES 5
+
+ extern fragS *mmix_opcode_frag;
+ #define TC_FRAG_TYPE fragS *
+ #define TC_FRAG_INIT(frag) (frag)->tc_frag_data = mmix_opcode_frag
+
+ /* We need to associate each section symbol with a list of GREGs defined
+    for that section/segment and sorted on offset, between the point where
+    all symbols have been evaluated and all frags mapped, and when the
+    fixups are done and relocs are output.  Similarly for each unknown
+    symbol.  */
+ extern void mmix_frob_file PARAMS ((void));
+ #define tc_frob_file mmix_frob_file
+
+ /* Used by mmix_frob_file.  Hangs on section symbols and unknown symbols.  */
+ struct mmix_symbol_gregs;
+ #define TC_SYMFIELD_TYPE struct mmix_symbol_gregs *
+
+ extern void mmix_frob_file_before_adjust PARAMS ((void));
+ #define tc_frob_file_before_adjust mmix_frob_file_before_adjust
+
+ extern void mmix_md_elf_section_change_hook PARAMS ((void));
+ #define md_elf_section_change_hook mmix_md_elf_section_change_hook
+
+ extern void mmix_md_do_align PARAMS ((int, char *, int, int));
+ #define md_do_align(n, fill, len, max, label) \
+  mmix_md_do_align (n, fill, len, max)
+
+ /* Each insn is a tetrabyte (4 bytes) long, but if there are BYTE
+    sequences sprinkled in, we can get unaligned DWARF2 offsets, so let's
+    explicitly say one byte.  */
+ #define DWARF2_LINE_MIN_INSN_LENGTH 1
*** /dev/null	Tue Jan  1 05:00:00 1980
--- config/tc-mmix.c	Sun Sep 30 18:39:10 2001
***************
*** 0 ****
--- 1,4180 ----
+ /* tc-mmix.c -- Assembler for Don Knuth's MMIX.
+    Copyright (C) 2001 Free Software Foundation.
+
+    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 2, 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, 59 Temple Place - Suite 330,
+    Boston, MA 02111-1307, 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 <stdio.h>
+ #include "as.h"
+ #include "subsegs.h"
+ #include "bfd.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 PARAMS ((char *));
+ static int get_operands PARAMS ((int, char *, expressionS[]));
+ static int get_putget_operands
+   PARAMS ((struct mmix_opcode *, char *, expressionS[]));
+ static void s_prefix PARAMS ((int));
+ static void s_greg PARAMS ((int));
+ static void s_loc PARAMS ((int));
+ static void s_bspec PARAMS ((int));
+ static void s_espec PARAMS ((int));
+ static void mmix_s_local PARAMS ((int));
+ static void mmix_greg_internal PARAMS ((char *));
+ static void mmix_set_geta_branch_offset PARAMS ((char *, offsetT value));
+ static void mmix_set_jmp_offset PARAMS ((char *, offsetT));
+ static void mmix_fill_nops PARAMS ((char *, int));
+ static int cmp_greg_symbol_fixes PARAMS ((const PTR, const PTR));
+ static int cmp_greg_val_greg_symbol_fixes
+   PARAMS ((const PTR p1, const PTR p2));
+ static void mmix_handle_rest_of_empty_line PARAMS ((void));
+ static void mmix_discard_rest_of_line PARAMS ((void));
+ static void mmix_byte PARAMS ((void));
+ static void mmix_cons PARAMS ((int));
+ static void mmix_frob_local_reloc PARAMS ((bfd *, asection *, PTR));
+
+ /* 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];
+
+ /* 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 expand operands for external symbols?  */
+ 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 emit built-in symbols?  */
+ static int predefined_syms = 1;
+
+ /* Should we 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;
+
+ /* 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 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)
+    {"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},
+    {NULL, no_argument, NULL, 0}
+  };
+
+ size_t md_longopts_size = sizeof (md_longopts);
+
+ static struct hash_control *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.
+
+    4. JMP
+       extra length: zero or four insns.  */
+
+ #define STATE_GETA	(1)
+ #define STATE_BCC	(2)
+ #define STATE_PUSHJ	(3)
+ #define STATE_JMP	(4)
+ #define STATE_GREG	(5)
+
+ /* 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 adress 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
+
+ #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).  */
+    {PUSHJ_0F,	PUSHJ_0B,	0,	ENCODE_RELAX (STATE_PUSHJ, STATE_MAX)},
+
+    /* 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}
+ };
+
+ 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},
+
+    /* Support DWARF2 debugging info.  */
+    {"file", dwarf2_directive_file, 0},
+    {"loc", dwarf2_directive_loc, 0},
+
+    {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 mmix_exp_chars[] = "eE";
+
+ const char mmix_flt_chars[] = "rf";
+
+
+ /* Fill in the offset-related part of GETA or Bcc.  */
+
+ static void
+ mmix_set_geta_branch_offset (opcodep, value)
+      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 (opcodep, value)
+      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 (opcodep, n)
+      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 (fn, exp)
+      void (*fn) PARAMS ((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 (max_operands, s, exp)
+      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 (name)
+      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 (insn, operands, exp)
+      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;
+
+   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_end ();
+ 	  sregend = input_line_pointer;
+ 	}
+     }
+   else
+     {
+       expp_sreg = &exp[0];
+       expp_reg = &exp[1];
+
+       /* Initialize to error state in case we'll never call expression on
+          this operand.  */
+       expp_reg->X_op = O_illegal;
+
+       sregp = p;
+       c = get_symbol_end ();
+       sregend = p = input_line_pointer;
+       *p = c;
+
+       /* 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 (c, arg)
+      int c;
+      char *arg ATTRIBUTE_UNUSED;
+ {
+   switch (c)
+     {
+     case 'x':
+       warn_on_expansion = 0;
+       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;
+
+     default:
+       return 0;
+     }
+
+   return 1;
+ }
+
+ /* Display MMIX-specific help text.  */
+
+ void
+ md_show_usage (stream)
+      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, _("\
+   -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.\n"));
+ }
+
+ /* Step to end of line, but don't step over the end of the line.  */
+
+ static void
+ mmix_discard_rest_of_line ()
+ {
+   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 ()
+ {
+   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 ()
+ {
+   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 = hash_new ();
+
+   real_reg_section
+     = bfd_make_section_old_way (stdoutput, MMIX_REG_SECTION_NAME);
+
+   for (opcode = mmix_opcodes; opcode->name; opcode++)
+     hash_insert (mmix_opcode_hash, opcode->name, (char *) opcode);
+
+   /* We always insert the ordinary registers 0..255 as registers.  */
+   for (i = 0; i < 256; i++)
+     {
+       char buf[5];
+
+       /* 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, i,
+ 				       &zero_address_frag));
+     }
+
+   /* 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,
+ 					 mmix_spec_regs[i].number + 256,
+ 					 &zero_address_frag));
+
+       /* FIXME: Perhaps these should be recognized as specials; as field
+ 	 names for those instructions.  */
+       symbol_table_insert (symbol_new ("ROUND_CURRENT", reg_section, 512,
+ 				       &zero_address_frag));
+       symbol_table_insert (symbol_new ("ROUND_OFF", reg_section, 512 + 1,
+ 				       &zero_address_frag));
+       symbol_table_insert (symbol_new ("ROUND_UP", reg_section, 512 + 2,
+ 				       &zero_address_frag));
+       symbol_table_insert (symbol_new ("ROUND_DOWN", reg_section, 512 + 3,
+ 				       &zero_address_frag));
+       symbol_table_insert (symbol_new ("ROUND_NEAR", reg_section, 512 + 4,
+ 				       &zero_address_frag));
+     }
+ }
+
+ /* Assemble one insn in STR.  */
+
+ void
+ md_assemble (str)
+      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 *) 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 determinable
+      from the first format character.  Currently *all* the information is
+      in the first character.  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;
+
+ 	  /* 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);
+
+ 	  /* 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.  */
+ 	  fix_new (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
+ 		   8, sym, 0, 0, BFD_RELOC_MMIX_BASE_PLUS_OFFSET);
+ 	  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.  Everyhing 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.  */
+       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*255*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 >> 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*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 >> 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 if (n_operands <= 3
+ 	       && (strcmp (instruction->name, "trip") == 0
+ 		   || strcmp (instruction->name, "trap") == 0))
+ 	{
+ 	  /* The meaning of operands to TRIP and TRAP are not defined, so
+ 	     we add combinations not handled above here as we find them.  */
+ 	  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
+ 		 maintanance 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.  */
+ 	      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_MMIX_REG_OR_BYTE);
+ 	      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_OR_BYTE);
+ 	      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_MMIX_REG_OR_BYTE);
+ 	    }
+ 	  else if (n_operands == 2)
+ 	    {
+ 	      if (exp[0].X_op == O_register)
+ 		opcodep[2] = exp[0].X_add_number;
+ 	      else
+ 		fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
+ 			     1, exp, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
+ 	      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_OR_BYTE);
+ 	    }
+ 	  else
+ 	    {
+ 	      as_bad (_("unsupported operands to %s: `%s'"),
+ 		      instruction->name, operands);
+ 	      return;
+ 	    }
+ 	}
+       else
+ 	{
+ 	  as_bad (_("invalid operands to opcode %s: `%s'"),
+ 		  instruction->name, operands);
+ 	  return;
+ 	}
+       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 (str)
+      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 (unused)
+      int unused ATTRIBUTE_UNUSED;
+ {
+   char *p;
+   int c;
+
+   SKIP_WHITESPACE ();
+
+   p = input_line_pointer;
+
+   c = get_symbol_end ();
+
+   /* Reseting 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;
+     }
+
+   *input_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 (shortname)
+      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 (label)
+      char *label;
+ {
+   expressionS *expP = &mmix_raw_gregs[n_of_raw_gregs].exp;
+
+   /* Don't set the section to register contents section before the
+      expression has been parsed; it may refer to the current position.  */
+   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;
+     }
+
+   /* 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 (unused)
+      int unused ATTRIBUTE_UNUSED;
+ {
+   char *p;
+   char c;
+   p = input_line_pointer;
+
+   /* 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_end ();
+
+   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 (unused)
+      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 (stdoutput, sec,
+ 				  bfd_get_section_flags (stdoutput, 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.  */
+   as_where (&bspec_file, &bspec_line);
+
+   doing_bspec = 1;
+ }
+
+ /* The "ESPEC" worker.  */
+
+ static void
+ s_espec (unused)
+      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 (unused)
+      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 (fragP, segment)
+      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)			\
+      {									\
+        /* 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_PUSHJ);
+       HANDLE_RELAXABLE (STATE_JMP);
+
+     case ENCODE_RELAX (STATE_GETA, STATE_ZERO):
+     case ENCODE_RELAX (STATE_BCC, STATE_ZERO):
+     case ENCODE_RELAX (STATE_PUSHJ, 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.  */
+
+ char *
+ md_atof (type, litP, sizeP)
+      int type;
+      char *litP;
+      int *sizeP;
+ {
+   int prec;
+   LITTLENUM_TYPE words[4];
+   char *t;
+   int i;
+
+   switch (type)
+     {
+       /* FIXME: Having 'f' in mmix_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.  */
+     case 'f':
+     case 'r':
+       prec = 2;
+       break;
+     case 'd':
+       prec = 4;
+       break;
+     default:
+       *sizeP = 0;
+       return _("bad call to md_atof");
+     }
+
+   t = atof_ieee (input_line_pointer, type, words);
+   if (t)
+     input_line_pointer = t;
+
+   *sizeP = prec * 2;
+
+   for (i = 0; i < prec; i++)
+     {
+       md_number_to_chars (litP, (valueT) words[i], 2);
+ 	  litP += 2;
+     }
+   return NULL;
+ }
+
+ /* Convert variable-sized frags into one or more fixups.  */
+
+ void
+ md_convert_frag (abfd, sec, fragP)
+      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_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)
+       {
+ 	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.  */
+
+ int
+ md_apply_fix3 (fixP, valp, segment)
+      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
+ 		      && symsec != real_reg_section)))))
+     {
+       fixP->fx_done = 0;
+       return 0;
+     }
+   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 0;
+     }
+   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:
+       /* 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) != real_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"));
+       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;
+
+       /* We don't want this "symbol" appearing in output, because that
+ 	 will fail.  */
+       if (fixP->fx_addsy
+ 	  && S_GET_SEGMENT (fixP->fx_addsy) == real_reg_section)
+ 	symbol_clear_used_in_reloc (fixP->fx_addsy);
+       break;
+
+     case BFD_RELOC_MMIX_REG:
+       if (fixP->fx_addsy == NULL
+ 	  || S_GET_SEGMENT (fixP->fx_addsy) != real_reg_section
+ 	  || S_GET_VALUE (fixP->fx_addsy) > 255)
+ 	as_bad_where (fixP->fx_file, fixP->fx_line,
+ 		      _("invalid operands"));
+       *buf = val;
+
+       if (fixP->fx_addsy
+ 	  && S_GET_SEGMENT (fixP->fx_addsy) == real_reg_section)
+ 	symbol_clear_used_in_reloc (fixP->fx_addsy);
+       break;
+
+     case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
+       /* These are never "fixed".  */
+       fixP->fx_done = 0;
+       return 0;
+
+     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;
+
+   return 0; /* Return value is ignored.  */
+ }
+
+ /* A bsearch function for looking up a value against offsets for GREG
+    definitions.  */
+
+ static int
+ cmp_greg_val_greg_symbol_fixes (p1, p2)
+      const PTR p1;
+      const PTR 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 (section, fixP)
+      asection *section ATTRIBUTE_UNUSED;
+      fixS *fixP;
+ {
+   bfd_signed_vma val
+     = fixP->fx_offset + (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);
+   bfd_vma addend = fixP->fx_offset;
+   asymbol *baddsy = addsy != NULL ? symbol_get_bfdsym (addsy) : NULL;
+
+   /* 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->owner, 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 (S_GET_SEGMENT (addsy)))
+ 	{
+ 	  /* Resolve this reloc now, as md_apply_fix3 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_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 (S_GET_SEGMENT (addsy))
+ 	      || strcmp (bfd_get_section_name (addsec->owner, 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 (S_GET_SEGMENT (addsy)))
+ 	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
+ 	      || S_GET_SEGMENT (addsy) == absolute_section))
+ 	buf[-3] |= IMM_OFFSET_BIT;
+
+       buf[0] = val;
+       return NULL;
+
+     case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
+       if (addsy != NULL
+ 	  &&  strcmp (bfd_get_section_name (addsec->owner, 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
+ 	  || S_GET_SEGMENT (addsy) != real_reg_section)
+ 	{
+ 	  struct mmix_symbol_gregs *gregs;
+ 	  struct mmix_symbol_greg_fixes *fix;
+
+ 	  if (S_IS_DEFINED (addsy))
+ 	    {
+ 	      if (! symbol_section_p (addsy)
+ 		  && ! bfd_is_abs_section (S_GET_SEGMENT (addsy)))
+ 		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 (S_GET_SEGMENT (addsy)))
+ 		{
+ 		  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 (S_GET_SEGMENT (addsy)))
+ 		{
+ 		  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.  FIXME: implement allocate-on-demand in
+ 	     the linker.  */
+ 	  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)
+ 	    {
+ 	      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 (S_GET_SEGMENT (addsy))
+ 	      || strcmp (bfd_get_section_name (addsec->owner, addsec),
+ 			 MMIX_REG_CONTENTS_SECTION_NAME) == 0))
+ 	{
+ 	  code = fixP->fx_r_type;
+ 	  break;
+ 	}
+
+       if (addsy != NULL
+ 	  && (S_GET_SEGMENT (addsy) != real_reg_section
+ 	      || val > 255
+ 	      || val < 0)
+ 	  && ! bfd_is_und_section (S_GET_SEGMENT (addsy)))
+ 	/* Drop through to error message.  */
+ 	;
+       else
+ 	{
+ 	  buf[0] = val;
+ 	  return NULL;
+ 	}
+       /* FALLTHROUGH. */
+
+       /* The others are supposed to be handled by md_apply_fix3.
+ 	 FIXME: ... which isn't called when -linkrelax.  Move over
+ 	 md_apply_fix3 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.  */
+       if (addsy)
+ 	symbol_clear_used_in_reloc (addsy);
+       return NULL;
+     }
+
+   relP = (arelent *) xmalloc (sizeof (arelent));
+   assert (relP != 0);
+   relP->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (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 ()
+ {
+   char *s0 = input_line_pointer;
+   char *s;
+   char *label = NULL;
+   char c;
+
+   if (pending_label != NULL)
+     as_fatal (_("internal: unhandled label %s"), pending_label);
+
+   if (mmix_gnu_syntax)
+     return;
+
+   /* 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.  */
+   if (input_line_pointer[0] == '.')
+     {
+       label_without_colon_this_line = 0;
+       return;
+     }
+
+   /* Don't handle empty lines here.  */
+   while (1)
+     {
+       if (*s0 == 0 || is_end_of_line [(unsigned int) *s0])
+ 	return;
+
+       if (! ISSPACE (*s0))
+ 	break;
+
+       s0++;
+     }
+
+   /* 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 (input_line_pointer[0])
+       && input_line_pointer[1] == 'H'
+       && ISSPACE (input_line_pointer[2]))
+     {
+       char *s;
+       current_fb_label = input_line_pointer[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.  */
+       input_line_pointer[1] = input_line_pointer[-1];
+       input_line_pointer += 2;
+
+       s = input_line_pointer;
+       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])
+ 	{
+ 	  char *name;
+ 	  unsigned int line;
+ 	  as_where (&name, &line);
+ 	  as_bad_where (name, line + 1,
+ 			_("[0-9]H labels may not appear alone on a line"));
+ 	  current_fb_label = -1;
+ 	}
+       if (*s == '.')
+ 	{
+ 	  char *name;
+ 	  unsigned int line;
+ 	  as_where (&name, &line);
+ 	  as_bad_where (name, line + 1,
+ 			_("[0-9]H labels do not mix with dot-pseudos"));
+ 	  current_fb_label = -1;
+ 	}
+     }
+   else
+     {
+       current_fb_label = -1;
+       if (is_name_beginner (input_line_pointer[0]))
+ 	label = input_line_pointer;
+     }
+
+   s0 = input_line_pointer;
+   /* Skip over label. */
+   while (*s0 && is_part_of_name (*s0))
+     s0++;
+
+   /* Remove trailing ":" off labels, as they'd otherwise be considered
+      part of the name.  But don't do it for local labels.  */
+   if (s0 != input_line_pointer && s0[-1] == ':'
+       && (s0 - 2 != input_line_pointer
+ 	  || ! ISDIGIT (s0[-2])))
+     s0[-1] = ' ';
+   else if (label != NULL)
+     {
+       /* 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.  */
+       int len_0 = s0 - 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;
+     }
+
+   while (*s0 && ISSPACE (*s0) && ! is_end_of_line [(unsigned int) *s0])
+     s0++;
+
+   if (pending_label != NULL && is_end_of_line [(unsigned int) *s0])
+     /* Whoops, this was actually a lone label on a line.  Like :-ended
+        labels, we don't attach such labels to the next instruction or
+        pseudo.  */
+     pending_label = NULL;
+
+   /* 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.  */
+   s = s0;
+   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 = s0;
+
+   /* 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]))
+ 	    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 = s0;
+   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
+ 	 they aren't bumped afterwards.  */
+       bump_line_counters ();
+
+       /* A fb-label is valid as an IS-label.  */
+       if (current_fb_label >= 0)
+ 	{
+ 	  char *fb_name;
+
+ 	  /* We need to save this name on our symbol obstack, since the
+ 	     string we got in fb_label_name is volatile and will change
+ 	     with every call to fb_label_name, like those resulting from
+ 	     parsing the IS-operand.  */
+ 	  fb_name = fb_label_name (current_fb_label, 1);
+ 	  obstack_grow (&mmix_sym_obstack, fb_name, strlen (fb_name) + 1);
+ 	  equals (obstack_finish (&mmix_sym_obstack), 0);
+ 	  fb_label_instance_inc (current_fb_label);
+ 	  current_fb_label = -1;
+ 	}
+       else
+ 	{
+ 	  if (pending_label == NULL)
+ 	    as_bad (_("empty label field for IS"));
+ 	  else
+ 	    equals (pending_label, 0);
+ 	  pending_label = NULL;
+ 	}
+
+       /* For mmixal, we can have comments without a comment-start
+ 	 character.   */
+       mmix_handle_rest_of_empty_line ();
+       input_line_pointer--;
+
+       input_line_pointer[-1] = ' ';
+     }
+   else if (s[0] == 'G'
+ 	   && s[1] == 'R'
+ 	   && strncmp (s, "GREG", 4) == 0
+ 	   && (ISSPACE (s[4]) || is_end_of_line[(unsigned char) s[4]]))
+     {
+       input_line_pointer = s + 4;
+
+       /* Right after this function ends, line numbers will be bumped if
+ 	 input_line_pointer[-1] = '\n'.  We want accurate line numbers for
+ 	 the s_greg call, so we bump them before the call, and make sure
+ 	 they aren't bumped afterwards.  */
+       bump_line_counters ();
+
+       /* A fb-label is valid as a GREG-label.  */
+       if (current_fb_label >= 0)
+ 	{
+ 	  char *fb_name;
+
+ 	  /* We need to save this name on our symbol obstack, since the
+ 	     string we got in fb_label_name is volatile and will change
+ 	     with every call to fb_label_name, like those resulting from
+ 	     parsing the IS-operand.  */
+ 	  fb_name = fb_label_name (current_fb_label, 1);
+
+ 	  /* Make sure we save the canonical name and don't get bitten by
+              prefixes.  */
+ 	  obstack_1grow (&mmix_sym_obstack, ':');
+ 	  obstack_grow (&mmix_sym_obstack, fb_name, strlen (fb_name) + 1);
+ 	  mmix_greg_internal (obstack_finish (&mmix_sym_obstack));
+ 	  fb_label_instance_inc (current_fb_label);
+ 	  current_fb_label = -1;
+ 	}
+       else
+ 	mmix_greg_internal (pending_label);
+
+       /* Back up before the end-of-line marker that was skipped in
+ 	 mmix_greg_internal.  */
+       input_line_pointer--;
+       input_line_pointer[-1] = ' ';
+
+       pending_label = NULL;
+     }
+   else if (pending_label != NULL)
+     {
+       input_line_pointer += strlen (pending_label);
+
+       /* See comment above about getting line numbers bumped.  */
+       input_line_pointer[-1] = '\n';
+     }
+ }
+
+ /* Give the value of an fb-label rewritten as in mmix_handle_mmixal, when
+    parsing an expression.
+
+    On valid calls, input_line_pointer points at a MAGIC_FB_BACKWARD_CHAR
+    or MAGIC_FB_BACKWARD_CHAR, followed by an ascii digit for the label.
+    We fill in the label as an expression.  */
+
+ void
+ mmix_fb_label (expP)
+      expressionS *expP;
+ {
+   symbolS *sym;
+   char *fb_internal_name;
+
+   /* This doesn't happen when not using mmixal syntax.  */
+   if (mmix_gnu_syntax
+       || (input_line_pointer[0] != MAGIC_FB_BACKWARD_CHAR
+ 	  && input_line_pointer[0] != MAGIC_FB_FORWARD_CHAR))
+     return;
+
+   /* The current backward reference has augmentation 0.  A forward
+      reference has augmentation 1, unless it's the same as a fb-label on
+      _this_ line, in which case we add one more so we don't refer to it.
+      This is the semantics of mmixal; it differs to that of common
+      fb-labels which refer to a here-label on the current line as a
+      backward reference.  */
+   fb_internal_name
+     = fb_label_name (input_line_pointer[1] - '0',
+ 		     (input_line_pointer[0] == MAGIC_FB_FORWARD_CHAR ? 1 : 0)
+ 		     + ((input_line_pointer[1] - '0' == current_fb_label
+ 			 && input_line_pointer[0] == MAGIC_FB_FORWARD_CHAR)
+ 			? 1 : 0));
+
+   input_line_pointer += 2;
+   sym = symbol_find_or_make (fb_internal_name);
+
+   /* We don't have to clean up unrelated fields here; we just do what the
+      expr machinery does, but *not* just what it does for [0-9][fb], since
+      we need to treat those as ordinary symbols sometimes; see testcases
+      err-byte2.s and fb-2.s.  */
+   if (S_GET_SEGMENT (sym) == absolute_section)
+     {
+       expP->X_op = O_constant;
+       expP->X_add_number = S_GET_VALUE (sym);
+     }
+   else
+     {
+       expP->X_op = O_symbol;
+       expP->X_add_symbol = sym;
+       expP->X_add_number = 0;
+     }
+ }
+
+ /* See whether we need to force a relocation into the output file.
+    This is used to force out switch and PC relative relocations when
+    relaxing.  */
+
+ int
+ mmix_force_relocation (fixP)
+      fixS * fixP;
+ {
+   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
+       || fixP->fx_r_type == BFD_RELOC_MMIX_BASE_PLUS_OFFSET)
+     return 1;
+
+   /* FIXME: This is dubious.  Handling of weak symbols should have been
+      caught before we get here.  */
+   if ((fixP->fx_addsy && S_IS_WEAK (fixP->fx_addsy)))
+     return 1;
+
+   if (linkrelax)
+     return 1;
+
+   /* All our pcrel relocations are must-keep.  Note that md_apply_fix3 is
+      called *after* this, and will handle getting rid of the presumed
+      reloc; a relocation isn't *forced* other than to be handled by
+      md_apply_fix3 (or tc_gen_reloc if linkrelax).  */
+   if (fixP->fx_pcrel)
+     return 1;
+
+   return 0;
+ }
+
+ /* The location from which a PC relative jump should be calculated,
+    given a PC relative reloc.  */
+
+ long
+ md_pcrel_from_section (fixP, sec)
+      fixS * fixP;
+      segT   sec;
+ {
+   if (fixP->fx_addsy != (symbolS *) NULL
+       && (! S_IS_DEFINED (fixP->fx_addsy)
+ 	  || S_GET_SEGMENT (fixP->fx_addsy) != sec))
+     {
+       /* The symbol is undefined (or is defined but not in this section).
+ 	 Let the linker figure it out.  */
+       return 0;
+     }
+
+   return (fixP->fx_frag->fr_address + fixP->fx_where);
+ }
+
+ /* Adjust the symbol table.  We make reg_section relative to the real
+    register section.
+
+    FIXME: There's a gas bug; should be fixed when the reg_section symbol
+    is "accidentally" saved for relocs which are really fixups that will be
+    fixed up.  */
+
+ void
+ mmix_adjust_symtab ()
+ {
+   symbolS *sym;
+   symbolS *prevsym;
+   symbolS *regsec = section_symbol (reg_section);
+   segT realregsec = NULL;
+
+   for (prevsym = sym = symbol_rootP;
+        sym != NULL;
+        prevsym = sym, sym = symbol_next (sym))
+     if (S_GET_SEGMENT (sym) == reg_section)
+       {
+ 	if (sym == regsec
+ 	    || (!S_IS_EXTERN (sym) && !symbol_used_in_reloc_p (sym)))
+ 	  {
+ 	    symbol_remove (sym, &symbol_rootP, &symbol_lastP);
+
+ 	    /* We make one extra turn, or we'll lose the next symbol.  We
+ 	       assume that the symbol we remove is not the symbol root
+ 	       (.text normally is).  */
+ 	    sym = prevsym;
+ 	  }
+ 	else
+ 	  {
+ 	    /* Change section to the *real* register section, so it gets
+ 	       proper treatment when writing it out.  Only do this for
+ 	       global symbols.  This also means we don't have to check for
+ 	       $0..$255.  */
+ 	    if (realregsec == NULL)
+ 	      realregsec
+ 		= bfd_make_section_old_way (stdoutput, MMIX_REG_SECTION_NAME);
+
+ 	    S_SET_SEGMENT (sym, realregsec);
+ 	  }
+       }
+ }
+
+ /* This is the expansion of LABELS_WITHOUT_COLONS.
+    We let md_start_line_hook tweak label_without_colon_this_line, and then
+    this function returns the tweaked value, and sets it to 1 for the next
+    line.  FIXME: Very, very brittle.  Not sure it works the way I
+    thought at the time I first wrote this.  */
+
+ int
+ mmix_label_without_colon_this_line ()
+ {
+   int retval = label_without_colon_this_line;
+
+   if (! mmix_gnu_syntax)
+     label_without_colon_this_line = 1;
+
+   return retval;
+ }
+
+ /* This is the expansion of md_relax_frag.  We go through the ordinary
+    relax table function except when the frag is for a GREG.  Then we have
+    to check whether there's another GREG by the same value that we can
+    join with.  */
+
+ long
+ mmix_md_relax_frag (seg, fragP, stretch)
+      segT seg;
+      fragS *fragP;
+      long stretch;
+ {
+   if (fragP->fr_subtype != STATE_GREG_DEF
+       && fragP->fr_subtype != STATE_GREG_UNDF)
+     return relax_frag (seg, fragP, stretch);
+
+   /* If we're defined, we don't grow.  */
+   if (fragP->fr_subtype == STATE_GREG_DEF)
+     return 0;
+
+   as_fatal (_("internal: unexpected relax type %d:%d"),
+ 	    fragP->fr_type, fragP->fr_subtype);
+   return 0;
+ }
+
+ /* Various things we punt until all input is seen.  */
+
+ void
+ mmix_md_end ()
+ {
+   fragS *fragP;
+   symbolS *mainsym;
+   int i;
+
+   /* The first frag of GREG:s going into the register contents section.  */
+   fragS *mmix_reg_contents_frags = NULL;
+
+   /* Reset prefix.  All labels reachable at this point must be
+      canonicalized.  */
+   mmix_current_prefix = NULL;
+
+   if (doing_bspec)
+     as_bad_where (bspec_file, bspec_line, _("BSPEC without ESPEC."));
+
+   /* Emit the low LOC setting of .text.  */
+   if (text_has_contents && lowest_text_loc != (bfd_vma) -1)
+     {
+       symbolS *symbolP;
+       char locsymbol[sizeof (":") - 1
+ 		    + sizeof (MMIX_LOC_SECTION_START_SYMBOL_PREFIX) - 1
+ 		    + sizeof (".text")];
+
+       /* An exercise in non-ISO-C-ness, this one.  */
+       sprintf (locsymbol, ":%s%s", MMIX_LOC_SECTION_START_SYMBOL_PREFIX,
+ 	       ".text");
+       symbolP
+ 	= symbol_new (locsymbol, absolute_section, lowest_text_loc,
+ 		      &zero_address_frag);
+       S_SET_EXTERNAL (symbolP);
+     }
+
+   /* Ditto .data.  */
+   if (data_has_contents && lowest_data_loc != (bfd_vma) -1)
+     {
+       symbolS *symbolP;
+       char locsymbol[sizeof (":") - 1
+ 		    + sizeof (MMIX_LOC_SECTION_START_SYMBOL_PREFIX) - 1
+ 		    + sizeof (".data")];
+
+       sprintf (locsymbol, ":%s%s", MMIX_LOC_SECTION_START_SYMBOL_PREFIX,
+ 	       ".data");
+       symbolP
+ 	= symbol_new (locsymbol, absolute_section, lowest_data_loc,
+ 		      &zero_address_frag);
+       S_SET_EXTERNAL (symbolP);
+     }
+
+   /* Unless GNU syntax mode, set "Main" to be a function, so the
+      disassembler doesn't get confused when we write truly
+      mmixal-compatible code (and don't use .type).  Similarly set it
+      global (regardless of -globalize-symbols), so the linker sees it as
+      the start symbol in ELF mode.  */
+   mainsym = symbol_find (MMIX_START_SYMBOL_NAME);
+   if (mainsym != NULL && ! mmix_gnu_syntax)
+     {
+       symbol_get_bfdsym (mainsym)->flags |= BSF_FUNCTION;
+       S_SET_EXTERNAL (mainsym);
+     }
+
+   if (n_of_raw_gregs != 0)
+     {
+       /* Emit GREGs.  They are collected in order of appearance, but must
+ 	 be emitted in opposite order to both have section address regno*8
+ 	 and the same allocation order (within a file) as mmixal.  */
+       segT this_segment = now_seg;
+       subsegT this_subsegment = now_subseg;
+       asection *regsec
+ 	= bfd_make_section_old_way (stdoutput,
+ 				    MMIX_REG_CONTENTS_SECTION_NAME);
+       subseg_set (regsec, 0);
+
+       /* Finally emit the initialization-value.  Emit a variable frag, which
+ 	 we'll fix in md_estimate_size_before_relax.  We set the initializer
+ 	 for the tc_frag_data field to NULL, so we can use that field for
+ 	 relaxation purposes.  */
+       mmix_opcode_frag = NULL;
+
+       frag_grow (0);
+       mmix_reg_contents_frags = frag_now;
+
+       for (i = n_of_raw_gregs - 1; i >= 0; i--)
+ 	{
+ 	  if (mmix_raw_gregs[i].label != NULL)
+ 	    /* There's a symbol.  Let it refer to this location in the
+ 	       register contents section.  The symbol must be globalized
+ 	       separately.  */
+ 	    colon (mmix_raw_gregs[i].label);
+
+ 	  frag_var (rs_machine_dependent, 8, 0, STATE_GREG_UNDF,
+ 		    make_expr_symbol (&mmix_raw_gregs[i].exp), 0, NULL);
+ 	}
+
+       subseg_set (this_segment, this_subsegment);
+     }
+
+   /* Iterate over frags resulting from GREGs and move those that evidently
+      have the same value together and point one to another.
+
+      This works in time O(N^2) but since the upper bound for non-error use
+      is 223, it's best to keep this simpler algorithm.  */
+   for (fragP = mmix_reg_contents_frags; fragP != NULL; fragP = fragP->fr_next)
+     {
+       fragS **fpp;
+       fragS *fp = NULL;
+       fragS *osymfrag;
+       offsetT osymval;
+       expressionS *oexpP;
+       symbolS *symbolP = fragP->fr_symbol;
+
+       if (fragP->fr_type != rs_machine_dependent
+ 	  || fragP->fr_subtype != STATE_GREG_UNDF)
+ 	continue;
+
+       /* Whatever the outcome, we will have this GREG judged merged or
+ 	 non-merged.  Since the tc_frag_data is NULL at this point, we
+ 	 default to non-merged.  */
+       fragP->fr_subtype = STATE_GREG_DEF;
+
+       /* If we're not supposed to merge GREG definitions, then just don't
+ 	 look for equivalents.  */
+       if (! merge_gregs)
+ 	continue;
+
+       osymval = (offsetT) S_GET_VALUE (symbolP);
+       osymfrag = symbol_get_frag (symbolP);
+
+       /* If the symbol isn't defined, we can't say that another symbol
+ 	 equals this frag, then.  FIXME: We can look at the "deepest"
+ 	 defined name; if a = c and b = c then obviously a == b.  */
+       if (! S_IS_DEFINED (symbolP))
+ 	continue;
+
+       oexpP = symbol_get_value_expression (fragP->fr_symbol);
+
+       /* If the initialization value is zero, then we must not merge them.  */
+       if (oexpP->X_op == O_constant && osymval == 0)
+ 	continue;
+
+       /* Iterate through the frags downward this one.  If we find one that
+ 	 has the same non-zero value, move it to after this one and point
+ 	 to it as the equivalent.  */
+       for (fpp = &fragP->fr_next; *fpp != NULL; fpp = &fpp[0]->fr_next)
+ 	{
+ 	  fp = *fpp;
+
+ 	  if (fp->fr_type != rs_machine_dependent
+ 	      || fp->fr_subtype != STATE_GREG_UNDF)
+ 	    continue;
+
+ 	  /* Calling S_GET_VALUE may simplify the symbol, changing from
+ 	     expr_section etc. so call it first.  */
+ 	  if ((offsetT) S_GET_VALUE (fp->fr_symbol) == osymval
+ 	      && symbol_get_frag (fp->fr_symbol) == osymfrag)
+ 	    {
+ 	      /* Move the frag links so the one we found equivalent comes
+ 		 after the current one, carefully considering that
+ 		 sometimes fpp == &fragP->fr_next and the moves must be a
+ 		 NOP then.  */
+ 	      *fpp = fp->fr_next;
+ 	      fp->fr_next = fragP->fr_next;
+ 	      fragP->fr_next = fp;
+ 	      break;
+ 	    }
+ 	}
+
+       if (*fpp != NULL)
+ 	fragP->tc_frag_data = fp;
+     }
+ }
+
+ /* qsort function for mmix_symbol_gregs.  */
+
+ static int
+ cmp_greg_symbol_fixes (parg, qarg)
+      const PTR parg;
+      const PTR qarg;
+ {
+   const struct mmix_symbol_greg_fixes *p
+     = (const struct mmix_symbol_greg_fixes *) parg;
+   const struct mmix_symbol_greg_fixes *q
+     = (const struct mmix_symbol_greg_fixes *) qarg;
+
+   return p->offs > q->offs ? 1 : p->offs < q->offs ? -1 : 0;
+ }
+
+ /* Collect GREG definitions from mmix_gregs and hang them as lists sorted
+    on increasing offsets onto each section symbol or undefined symbol.
+
+    Also, remove the register convenience section so it doesn't get output
+    as an ELF section.  */
+
+ void
+ mmix_frob_file ()
+ {
+   int i;
+   struct mmix_symbol_gregs *all_greg_symbols[MAX_GREGS];
+   int n_greg_symbols = 0;
+
+   /* Collect all greg fixups and decorate each corresponding symbol with
+      the greg fixups for it.  */
+   for (i = 0; i < n_of_cooked_gregs; i++)
+     {
+       offsetT offs;
+       symbolS *sym;
+       struct mmix_symbol_gregs *gregs;
+       fixS *fixP;
+
+       fixP = mmix_gregs[i];
+       know (fixP->fx_r_type == BFD_RELOC_64);
+
+       /* This case isn't doable in general anyway, methinks.  */
+       if (fixP->fx_subsy != NULL)
+ 	{
+ 	  as_bad_where (fixP->fx_file, fixP->fx_line,
+ 			_("GREG expression too complicated"));
+ 	  continue;
+ 	}
+
+       sym = fixP->fx_addsy;
+       offs = (offsetT) fixP->fx_offset;
+
+       /* If the symbol is defined, then it must be resolved to a section
+ 	 symbol at this time, or else we don't know how to handle it.  */
+       if (S_IS_DEFINED (sym))
+ 	{
+ 	  if (! symbol_section_p (sym)
+ 	      && ! bfd_is_abs_section (S_GET_SEGMENT (sym)))
+ 	    as_fatal (_("internal: GREG expression not resolved to section"));
+
+ 	  offs += S_GET_VALUE (sym);
+ 	}
+
+       /* If this is an absolute symbol sufficiently near lowest_data_loc,
+ 	 then we canonicalize on the data section.  Note that offs 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) offs + 256 > lowest_data_loc
+ 	  && bfd_is_abs_section (S_GET_SEGMENT (sym)))
+ 	{
+ 	  offs -= (offsetT) lowest_data_loc;
+ 	  sym = section_symbol (data_section);
+ 	}
+       /* Likewise text section.  */
+       else if (lowest_text_loc != (bfd_vma) -1
+ 	       && (bfd_vma) offs + 256 > lowest_text_loc
+ 	       && bfd_is_abs_section (S_GET_SEGMENT (sym)))
+ 	{
+ 	  offs -= (offsetT) lowest_text_loc;
+ 	  sym = section_symbol (text_section);
+ 	}
+
+       gregs = *symbol_get_tc (sym);
+
+       if (gregs == NULL)
+ 	{
+ 	  gregs = xmalloc (sizeof (*gregs));
+ 	  gregs->n_gregs = 0;
+ 	  symbol_set_tc (sym, &gregs);
+ 	  all_greg_symbols[n_greg_symbols++] = gregs;
+ 	}
+
+       gregs->greg_fixes[gregs->n_gregs].fix = fixP;
+       gregs->greg_fixes[gregs->n_gregs++].offs = offs;
+     }
+
+   /* For each symbol having a GREG definition, sort those definitions on
+      offset.  */
+   for (i = 0; i < n_greg_symbols; i++)
+     qsort (all_greg_symbols[i]->greg_fixes, all_greg_symbols[i]->n_gregs,
+ 	   sizeof (all_greg_symbols[i]->greg_fixes[0]), cmp_greg_symbol_fixes);
+
+   if (real_reg_section != NULL)
+     {
+       asection **secpp;
+
+       /* FIXME: Pass error state gracefully.  */
+       if (bfd_get_section_flags (stdoutput, real_reg_section) & SEC_HAS_CONTENTS)
+ 	as_fatal (_("register section has contents\n"));
+
+       /* FIXME: This does not seem like the proper way to kill a section,
+ 	 but it's the way it's done elsewhere, like elf64-alpha.c.  */
+       /* Really remove the section.  */
+       for (secpp = &stdoutput->sections;
+ 	   *secpp != real_reg_section;
+ 	   secpp = &(*secpp)->next)
+ 	;
+       *secpp = (*secpp)->next;
+       --stdoutput->section_count;
+     }
+
+ }
+
+ /* Provide an expression for a built-in name provided when-used.
+    Either a symbol that is a handler; living in 0x10*[1..8] and having
+    name [DVWIOUZX]_Handler, or a mmixal built-in symbol.
+
+    If the name isn't a built-in name and parsed into *EXPP, return zero.  */
+
+ int
+ mmix_parse_predefined_name (name, expP)
+      char *name;
+      expressionS *expP;
+ {
+   char *canon_name;
+   char *handler_charp;
+   const char handler_chars[] = "DVWIOUZX";
+   symbolS *symp;
+
+   if (! predefined_syms)
+     return 0;
+
+   canon_name = tc_canonicalize_symbol_name (name);
+
+   if (canon_name[1] == '_'
+       && strcmp (canon_name + 2, "Handler") == 0
+       && (handler_charp = strchr (handler_chars, *canon_name)) != NULL)
+     {
+       /* If the symbol doesn't exist, provide one relative to the .text
+ 	 section.
+
+ 	 FIXME: We should provide separate sections, mapped in the linker
+ 	 script.  */
+       symp = symbol_find (name);
+       if (symp == NULL)
+ 	symp = symbol_new (name, text_section,
+ 			   0x10 * (handler_charp + 1 - handler_chars),
+ 			   &zero_address_frag);
+     }
+   else
+     {
+       /* These symbols appear when referenced; needed for
+          mmixal-compatible programs.  */
+       unsigned int i;
+
+       static const struct
+       {
+ 	const char *name;
+ 	valueT val;
+       } predefined_abs_syms[] =
+ 	{
+ 	  {"Data_Segment", (valueT) 0x20 << 56},
+ 	  {"Pool_Segment", (valueT) 0x40 << 56},
+ 	  {"Stack_Segment", (valueT) 0x60 << 56},
+ 	  {"StdIn", 0},
+ 	  {"StdOut", 1},
+ 	  {"StdErr", 2},
+ 	  {"TextRead", 0},
+ 	  {"TextWrite", 1},
+ 	  {"BinaryRead", 2},
+ 	  {"BinaryWrite", 3},
+ 	  {"BinaryReadWrite", 4},
+ 	  {"Halt", 0},
+ 	  {"Fopen", 1},
+ 	  {"Fclose", 2},
+ 	  {"Fread", 3},
+ 	  {"Fgets", 4},
+ 	  {"Fgetws", 5},
+ 	  {"Fwrite", 6},
+ 	  {"Fputs", 7},
+ 	  {"Fputws", 8},
+ 	  {"Fseek", 9},
+ 	  {"Ftell", 10},
+ 	  {"D_BIT", 0x80},
+ 	  {"V_BIT", 0x40},
+ 	  {"W_BIT", 0x20},
+ 	  {"I_BIT", 0x10},
+ 	  {"O_BIT", 0x08},
+ 	  {"U_BIT", 0x04},
+ 	  {"Z_BIT", 0x02},
+ 	  {"X_BIT", 0x01},
+ 	  {"Inf", 0x7ff00000}
+ 	};
+
+       /* If it's already in the symbol table, we shouldn't do anything.  */
+       symp = symbol_find (name);
+       if (symp != NULL)
+ 	return 0;
+
+       for (i = 0;
+ 	   i < sizeof (predefined_abs_syms)/sizeof (predefined_abs_syms[0]);
+ 	   i++)
+ 	if (strcmp (canon_name, predefined_abs_syms[i].name) == 0)
+ 	  {
+ 	    symbol_table_insert (symbol_new (predefined_abs_syms[i].name,
+ 					     absolute_section,
+ 					     predefined_abs_syms[i].val,
+ 					     &zero_address_frag));
+
+ 	    /* Let gas find the symbol we just created, through its
+                ordinary lookup.  */
+ 	    return 0;
+ 	  }
+
+       /* Not one of those symbols.  Let gas handle it.  */
+       return 0;
+     }
+
+   expP->X_op = O_symbol;
+   expP->X_add_number = 0;
+   expP->X_add_symbol = symp;
+   expP->X_op_symbol = NULL;
+
+   return 1;
+ }
+
+ /* Worker for mmix_frob_file_before_adjust.  */
+
+ static void
+ mmix_frob_local_reloc (abfd, sec, xxx)
+      bfd *abfd ATTRIBUTE_UNUSED;
+      asection *sec;
+      PTR xxx ATTRIBUTE_UNUSED;
+ {
+   segment_info_type *seginfo = seg_info (sec);
+   fixS *fixp;
+
+   if (seginfo == NULL)
+     return;
+
+   for (fixp = seginfo->fix_root; fixp; fixp = fixp->fx_next)
+     if (! fixp->fx_done && fixp->fx_addsy != NULL)
+       {
+ 	symbolS *sym = fixp->fx_addsy;
+ 	asection *section = S_GET_SEGMENT (sym);
+
+ 	if (section == reg_section
+ 	    && fixp->fx_r_type == BFD_RELOC_MMIX_LOCAL)
+ 	  {
+ 	    /* If the register is marked global, we don't need to replace
+ 	       with the *real* register section since that will be done
+ 	       when the symbol is changed.  */
+ 	    if (! S_IS_EXTERNAL (sym))
+ 	      /* If it's a local symbol, we replace it with an anonymous
+ 		 one with the same constant value.  */
+ 	      fixp->fx_addsy = expr_build_uconstant (S_GET_VALUE (sym));
+ 	  }
+       }
+ }
+
+ /* Change fixups for register symbols for BFD_MMIX_LOCAL to be for an
+    absolute symbol.  */
+
+ void
+ mmix_frob_file_before_adjust ()
+ {
+   return;
+   bfd_map_over_sections (stdoutput, mmix_frob_local_reloc, (char *) 0);
+ }
+
+ /* Just check that we don't have a BSPEC/ESPEC pair active when changing
+    sections "normally", and get knowledge about alignment from the new
+    section.  */
+
+ void
+ mmix_md_elf_section_change_hook ()
+ {
+   if (doing_bspec)
+     as_bad (_("section change from within a BSPEC/ESPEC pair is not supported"));
+
+   last_alignment = bfd_get_section_alignment (now_seg->owner, now_seg);
+   want_unaligned = 0;
+ }
+
+ /* The LOC worker.  This is like s_org, but we have to support changing
+    section too.   */
+
+ static void
+ s_loc (ignore)
+      int ignore ATTRIBUTE_UNUSED;
+ {
+   segT section;
+   expressionS exp;
+   char *p;
+   symbolS *sym;
+   offsetT off;
+
+   /* Must not have a BSPEC in progress. */
+   if (doing_bspec)
+     {
+       as_bad (_("directive LOC from within a BSPEC/ESPEC pair is not supported"));
+       return;
+     }
+
+   section = expression (&exp);
+
+   if (exp.X_op == O_illegal
+       || exp.X_op == O_absent
+       || exp.X_op == O_big
+       || section == undefined_section)
+     {
+       as_bad (_("invalid LOC expression"));
+       return;
+     }
+
+   if (section == absolute_section)
+     {
+       /* Translate a constant into a suitable section.  */
+
+       if (exp.X_add_number < ((offsetT) 0x20 << 56))
+ 	{
+ 	  /* Lower than Data_Segment - assume it's .text.  */
+ 	  section = text_section;
+
+ 	  /* Save the lowest seen location, so we can pass on this
+ 	     information to the linker.  We don't actually org to this
+ 	     location here, we just pass on information to the linker so
+ 	     it can put the code there for us.  */
+
+ 	  /* If there was already a loc (that has to be set lower than
+ 	     this one), we org at (this - lower).  There's an implicit
+ 	     "LOC 0" before any entered code.  FIXME: handled by spurious
+ 	     settings of text_has_contents.  */
+ 	  if (exp.X_add_number < 0
+ 	      || exp.X_add_number < (offsetT) lowest_text_loc)
+ 	    {
+ 	      as_bad (_("LOC expression stepping backwards is not supported"));
+ 	      exp.X_op = O_absent;
+ 	    }
+ 	  else
+ 	    {
+ 	      if (text_has_contents && lowest_text_loc == (bfd_vma) -1)
+ 		lowest_text_loc = 0;
+
+ 	      if (lowest_text_loc == (bfd_vma) -1)
+ 		{
+ 		  lowest_text_loc = exp.X_add_number;
+
+ 		  /* We want only to change the section, not set an offset.  */
+ 		  exp.X_op = O_absent;
+ 		}
+ 	      else
+ 		exp.X_add_number -= lowest_text_loc;
+ 	    }
+ 	}
+       else
+ 	{
+ 	  /* Do the same for the .data section.  */
+ 	  section = data_section;
+
+ 	  if (exp.X_add_number < (offsetT) lowest_data_loc)
+ 	    {
+ 	      as_bad (_("LOC expression stepping backwards is not supported"));
+ 	      exp.X_op = O_absent;
+ 	    }
+ 	  else
+ 	    {
+ 	      if (data_has_contents && lowest_data_loc == (bfd_vma) -1)
+ 		lowest_data_loc = (bfd_vma) 0x20 << 56;
+
+ 	      if (lowest_data_loc == (bfd_vma) -1)
+ 		{
+ 		  lowest_data_loc = exp.X_add_number;
+
+ 		  /* We want only to change the section, not set an offset.  */
+ 		  exp.X_op = O_absent;
+ 		}
+ 	      else
+ 		exp.X_add_number -= lowest_data_loc;
+ 	    }
+ 	}
+     }
+
+   if (section != now_seg)
+     {
+       obj_elf_section_change_hook ();
+       subseg_set (section, 0);
+
+       /* Call our section change hooks using the official hook.  */
+       md_elf_section_change_hook ();
+     }
+
+   if (exp.X_op != O_absent)
+     {
+       if (exp.X_op != O_constant && exp.X_op != O_symbol)
+ 	{
+ 	  /* Handle complex expressions.  */
+ 	  sym = make_expr_symbol (&exp);
+ 	  off = 0;
+ 	}
+       else
+ 	{
+ 	  sym = exp.X_add_symbol;
+ 	  off = exp.X_add_number;
+ 	}
+
+       p = frag_var (rs_org, 1, 1, (relax_substateT) 0, sym, off, (char *) 0);
+       *p = 0;
+     }
+
+   mmix_handle_rest_of_empty_line ();
+ }
+
+ /* The BYTE worker.  We have to support sequences of mixed "strings",
+    numbers and other constant "first-pass" reducible expressions separated
+    by comma.  */
+
+ static void
+ mmix_byte ()
+ {
+   unsigned int c;
+   char *start;
+
+   if (now_seg == text_section)
+     text_has_contents = 1;
+   else if (now_seg == data_section)
+     data_has_contents = 1;
+
+   do
+     {
+       SKIP_WHITESPACE ();
+       switch (*input_line_pointer)
+ 	{
+ 	case '\"':
+ 	  ++input_line_pointer;
+ 	  start = input_line_pointer;
+ 	  while (is_a_char (c = next_char_of_string ()))
+ 	    {
+ 	      FRAG_APPEND_1_CHAR (c);
+ 	    }
+
+ 	  if (input_line_pointer[-1] != '\"')
+ 	    {
+ 	      /* We will only get here in rare cases involving #NO_APP,
+ 		 where the unterminated string is not recognized by the
+ 		 preformatting pass.  */
+ 	      as_bad (_("unterminated string"));
+ 	      mmix_discard_rest_of_line ();
+ 	      return;
+ 	    }
+ 	  break;
+
+ 	default:
+ 	  {
+ 	    expressionS exp;
+ 	    segT expseg = expression (&exp);
+
+ 	    /* We have to allow special register names as constant numbers.  */
+ 	    if ((expseg != absolute_section && expseg != reg_section)
+ 		|| (exp.X_op != O_constant
+ 		    && (exp.X_op != O_register
+ 			|| exp.X_add_number <= 255)))
+ 	      {
+ 		as_bad (_("BYTE expression not a pure number"));
+ 		mmix_discard_rest_of_line ();
+ 		return;
+ 	      }
+ 	    else if ((exp.X_add_number > 255 && exp.X_op != O_register)
+ 		     || exp.X_add_number < 0)
+ 	      {
+ 		/* Note that mmixal does not allow negative numbers in
+ 		   BYTE sequences, so neither should we.  */
+ 		as_bad (_("BYTE expression not in the range 0..255"));
+ 		mmix_discard_rest_of_line ();
+ 		return;
+ 	      }
+
+ 	    FRAG_APPEND_1_CHAR (exp.X_add_number);
+ 	  }
+ 	  break;
+ 	}
+
+       SKIP_WHITESPACE ();
+       c = *input_line_pointer++;
+     }
+   while (c == ',');
+
+   input_line_pointer--;
+
+   if (mmix_gnu_syntax)
+     demand_empty_rest_of_line ();
+   else
+     {
+       mmix_discard_rest_of_line ();
+       /* Do like demand_empty_rest_of_line and step over the end-of-line
+          boundary.  */
+       input_line_pointer++;
+     }
+
+   /* Make sure we align for the next instruction.  */
+   last_alignment = 0;
+ }
+
+ /* Like cons_worker, but we have to ignore "naked comments", not barf on
+    them.  Implements WYDE, TETRA and OCTA.  We're a little bit more
+    lenient than mmix_byte but FIXME: they should eventually merge.  */
+
+ static void
+ mmix_cons (nbytes)
+      int nbytes;
+ {
+   expressionS exp;
+   char *start;
+
+   /* If we don't have any contents, then it's ok to have a specified start
+      address that is not a multiple of the max data size.  We will then
+      align it as necessary when we get here.  Otherwise, it's a fatal sin.  */
+   if (now_seg == text_section)
+     {
+       if (lowest_text_loc != (bfd_vma) -1
+ 	  && (lowest_text_loc & (nbytes - 1)) != 0)
+ 	{
+ 	  if (text_has_contents)
+ 	    as_bad (_("data item with alignment larger than location"));
+ 	  else if (want_unaligned)
+ 	    as_bad (_("unaligned data at an absolute location is not supported"));
+
+ 	  lowest_text_loc &= ~((bfd_vma) nbytes - 1);
+ 	  lowest_text_loc += (bfd_vma) nbytes;
+ 	}
+
+       text_has_contents = 1;
+     }
+   else if (now_seg == data_section)
+     {
+       if (lowest_data_loc != (bfd_vma) -1
+ 	  && (lowest_data_loc & (nbytes - 1)) != 0)
+ 	{
+ 	  if (data_has_contents)
+ 	    as_bad (_("data item with alignment larger than location"));
+ 	  else if (want_unaligned)
+ 	    as_bad (_("unaligned data at an absolute location is not supported"));
+
+ 	  lowest_data_loc &= ~((bfd_vma) nbytes - 1);
+ 	  lowest_data_loc += (bfd_vma) nbytes;
+ 	}
+
+       data_has_contents = 1;
+     }
+
+   /* Always align these unless asked not to (valid for the current pseudo).  */
+   if (! want_unaligned)
+     {
+       last_alignment = nbytes == 2 ? 1 : (nbytes == 4 ? 2 : 3);
+       frag_align (last_alignment, 0, 0);
+       record_alignment (now_seg, last_alignment);
+     }
+
+   /* 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 (current_fb_label >= 0)
+     colon (fb_label_name (current_fb_label, 1));
+   else if (pending_label != NULL)
+     {
+       colon (pending_label);
+       pending_label = NULL;
+     }
+
+   SKIP_WHITESPACE ();
+
+   if (is_end_of_line [(unsigned int) *input_line_pointer])
+     {
+       /* Default to zero if the expression was absent.  */
+
+       exp.X_op = O_constant;
+       exp.X_add_number = 0;
+       exp.X_unsigned = 0;
+       exp.X_add_symbol = NULL;
+       exp.X_op_symbol = NULL;
+       emit_expr (&exp, (unsigned int) nbytes);
+     }
+   else
+     do
+       {
+ 	unsigned int c;
+
+ 	switch (*input_line_pointer)
+ 	  {
+ 	    /* We support strings here too; each character takes up nbytes
+ 	       bytes.  */
+ 	  case '\"':
+ 	    ++input_line_pointer;
+ 	    start = input_line_pointer;
+ 	    while (is_a_char (c = next_char_of_string ()))
+ 	      {
+ 		exp.X_op = O_constant;
+ 		exp.X_add_number = c;
+ 		exp.X_unsigned = 1;
+ 		emit_expr (&exp, (unsigned int) nbytes);
+ 	      }
+
+ 	    if (input_line_pointer[-1] != '\"')
+ 	      {
+ 		/* We will only get here in rare cases involving #NO_APP,
+ 		   where the unterminated string is not recognized by the
+ 		   preformatting pass.  */
+ 		as_bad (_("unterminated string"));
+ 		mmix_discard_rest_of_line ();
+ 		return;
+ 	      }
+ 	    break;
+
+ 	  default:
+ 	    {
+ 	      expression (&exp);
+ 	      emit_expr (&exp, (unsigned int) nbytes);
+ 	      SKIP_WHITESPACE ();
+ 	    }
+ 	    break;
+ 	  }
+       }
+     while (*input_line_pointer++ == ',');
+
+   input_line_pointer--;		/* Put terminator back into stream.  */
+
+   mmix_handle_rest_of_empty_line ();
+
+   /* We don't need to step up the counter for the current_fb_label here;
+      that's handled by the caller.  */
+ }
+
+ /* The md_do_align worker.  At present, we just record an alignment to
+    nullify the automatic alignment we do for WYDE, TETRA and OCTA, as gcc
+    does not use the unaligned macros when attribute packed is used.
+    Arguably this is a GCC bug.  */
+
+ void
+ mmix_md_do_align (n, fill, len, max)
+      int n;
+      char *fill ATTRIBUTE_UNUSED;
+      int len ATTRIBUTE_UNUSED;
+      int max ATTRIBUTE_UNUSED;
+ {
+   last_alignment = n;
+   want_unaligned = n == 0;
+ }

brgds, H-P


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]