]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gas/config/tc-mmix.c
Update year range in copyright notice of binutils files
[thirdparty/binutils-gdb.git] / gas / config / tc-mmix.c
index b76980789c4c43a08c3c884fb26e708a84c96c09..e6efea66d0bad535a4b4cb67bf45edbc45d4355d 100644 (file)
@@ -1,11 +1,11 @@
 /* tc-mmix.c -- Assembler for Don Knuth's MMIX.
-   Copyright (C) 2001 Free Software Foundation.
+   Copyright (C) 2001-2021 Free Software Foundation, Inc.
 
    This file is part of GAS, the GNU Assembler.
 
    GAS is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
+   the Free Software Foundation; either version 3, or (at your option)
    any later version.
 
    GAS is distributed in the hope that it will be useful,
@@ -15,8 +15,8 @@
 
    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.  */
+   the Free Software Foundation, 51 Franklin Street - Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
 
 /* Knuth's assembler mmixal does not provide a relocatable format; mmo is
    to be considered a final link-format.  In the final link, we make mmo,
    compatible syntax, but the main purpose is to serve GCC.  */
 
 
-#include <stdio.h>
 #include "as.h"
+#include <limits.h>
 #include "subsegs.h"
-#include "bfd.h"
 #include "elf/mmix.h"
 #include "opcode/mmix.h"
 #include "safe-ctype.h"
    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
- };
+{
+  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));
+static int get_spec_regno (char *);
+static int get_operands (int, char *, expressionS *);
+static int get_putget_operands (struct mmix_opcode *, char *, expressionS *);
+static void s_prefix (int);
+static void s_greg (int);
+static void s_loc (int);
+static void s_bspec (int);
+static void s_espec (int);
+static void mmix_s_local (int);
+static void mmix_greg_internal (char *);
+static void mmix_set_geta_branch_offset (char *, offsetT);
+static void mmix_set_jmp_offset (char *, offsetT);
+static void mmix_fill_nops (char *, int);
+static int cmp_greg_symbol_fixes (const void *, const void *);
+static int cmp_greg_val_greg_symbol_fixes (const void *, const void *);
+static void mmix_handle_rest_of_empty_line (void);
+static void mmix_discard_rest_of_line (void);
+static void mmix_byte (void);
+static void mmix_cons (int);
 
 /* Continue the tradition of symbols.c; use control characters to enforce
    magic.  These are used when replacing e.g. 8F and 8B so we can handle
@@ -112,6 +108,14 @@ static struct
    expressionS exp;
  } mmix_raw_gregs[MAX_GREGS];
 
+static struct loc_assert_s
+ {
+   segT old_seg;
+   symbolS *loc_sym;
+   fragS *frag;
+   struct loc_assert_s *next;
+ } *loc_asserts = NULL;
+
 /* Fixups for all unique GREG registers.  We store the fixups here in
    md_convert_frag, then we use the array to convert
    BFD_RELOC_MMIX_BASE_PLUS_OFFSET fixups in tc_gen_reloc.  The index is
@@ -144,7 +148,8 @@ struct mmix_symbol_gregs
    this line?  */
 static int label_without_colon_this_line = 1;
 
-/* Should we expand operands for external symbols?  */
+/* Should we automatically expand instructions into multiple insns in
+   order to generate working code?  */
 static int expand_op = 1;
 
 /* Should we warn when expanding operands?  FIXME: test-cases for when -x
@@ -154,11 +159,15 @@ static int warn_on_expansion = 1;
 /* Should we merge non-zero GREG register definitions?  */
 static int merge_gregs = 1;
 
+/* Should we pass on undefined BFD_RELOC_MMIX_BASE_PLUS_OFFSET relocs
+   (missing suitable GREG definitions) to the linker?  */
+static int allocate_undefined_gregs_in_linker = 0;
+
 /* Should we emit built-in symbols?  */
 static int predefined_syms = 1;
 
-/* Should we anything but the listed special register name (e.g. equated
-   symbols)?  */
+/* Should we allow anything but the listed special register name
+   (e.g. equated symbols)?  */
 static int equated_spec_regs = 1;
 
 /* Do we require standard GNU syntax?  */
@@ -167,13 +176,17 @@ int mmix_gnu_syntax = 0;
 /* Do we globalize all symbols?  */
 int mmix_globalize_symbols = 0;
 
+/* When expanding insns, do we want to expand PUSHJ as a call to a stub
+   (or else as a series of insns)?  */
+int pushj_stubs = 1;
+
 /* Do we know that the next semicolon is at the end of the operands field
-   (in mmixal mode; constant 1 in GNU mode)? */
+   (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 const char *bspec_file;
 static unsigned int bspec_line;
 
 struct option md_longopts[] =
@@ -185,6 +198,8 @@ struct option md_longopts[] =
 #define OPTION_GNU_SYNTAX  (OPTION_NOSYMS + 1)
 #define OPTION_GLOBALIZE_SYMBOLS  (OPTION_GNU_SYNTAX + 1)
 #define OPTION_FIXED_SPEC_REGS  (OPTION_GLOBALIZE_SYMBOLS + 1)
+#define OPTION_LINKER_ALLOCATED_GREGS  (OPTION_FIXED_SPEC_REGS + 1)
+#define OPTION_NOPUSHJSTUBS  (OPTION_LINKER_ALLOCATED_GREGS + 1)
    {"linkrelax", no_argument, NULL, OPTION_RELAX},
    {"no-expand", no_argument, NULL, OPTION_NOEXPAND},
    {"no-merge-gregs", no_argument, NULL, OPTION_NOMERGEGREG},
@@ -193,12 +208,16 @@ struct option md_longopts[] =
    {"globalize-symbols", no_argument, NULL, OPTION_GLOBALIZE_SYMBOLS},
    {"fixed-special-register-names", no_argument, NULL,
     OPTION_FIXED_SPEC_REGS},
+   {"linker-allocated-gregs", no_argument, NULL,
+    OPTION_LINKER_ALLOCATED_GREGS},
+   {"no-pushj-stubs", no_argument, NULL, OPTION_NOPUSHJSTUBS},
+   {"no-stubs", no_argument, NULL, OPTION_NOPUSHJSTUBS},
    {NULL, no_argument, NULL, 0}
  };
 
 size_t md_longopts_size = sizeof (md_longopts);
 
-static struct hash_control *mmix_opcode_hash;
+static htab_t mmix_opcode_hash;
 
 /* We use these when implementing the PREFIX pseudo.  */
 char *mmix_current_prefix;
@@ -221,15 +240,27 @@ struct obstack mmix_sym_obstack;
 
    3. PUSHJ
       extra length: zero or four insns.
+      Special handling to deal with transition to PUSHJSTUB.
 
    4. JMP
-      extra length: zero or four insns.  */
+      extra length: zero or four insns.
+
+   5. GREG
+      special handling, allocates a named global register unless another
+      is within reach for all uses.
+
+   6. PUSHJSTUB
+      special handling (mostly) for external references; assumes the
+      linker will generate a stub if target is no longer than 256k from
+      the end of the section plus max size of previous stubs.  Zero or
+      four insns.  */
 
 #define STATE_GETA     (1)
 #define STATE_BCC      (2)
 #define STATE_PUSHJ    (3)
 #define STATE_JMP      (4)
 #define STATE_GREG     (5)
+#define STATE_PUSHJSTUB        (6)
 
 /* No fine-grainedness here.  */
 #define STATE_LENGTH_MASK          (1)
@@ -248,35 +279,40 @@ struct obstack mmix_sym_obstack;
 #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
+/* These displacements are relative to the address following the opcode
    word of the instruction.  The catch-all states have zero for "reach"
    and "next" entries.  */
 
 #define GETA_0F (65536 * 4 - 8)
 #define GETA_0B (-65536 * 4 - 4)
 
-#define GETA_MAX_LEN 4*4
+#define GETA_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_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_MAX_LEN 5 * 4
 #define PUSHJ_4F GETA_3F
 #define PUSHJ_4B GETA_3B
 
+/* We'll very rarely have sections longer than LONG_MAX, but we'll make a
+   feeble attempt at getting 64-bit values.  */
+#define PUSHJSTUB_MAX ((offsetT) (((addressT) -1) >> 1))
+#define PUSHJSTUB_MIN (-PUSHJSTUB_MAX - 1)
+
 #define JMP_0F (65536 * 256 * 4 - 8)
 #define JMP_0B (-65536 * 256 * 4 - 4)
 
-#define JMP_MAX_LEN 5*4
+#define JMP_MAX_LEN 5 * 4
 #define JMP_4F 0
 #define JMP_4B 0
 
@@ -305,8 +341,8 @@ const relax_typeS mmix_relax_table[] =
    {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, 0).  Next state is actually PUSHJSTUB (6, 0).  */
+   {PUSHJ_0F,  PUSHJ_0B,       0,      ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO)},
 
    /* PUSHJ (3, 1).  */
    {PUSHJ_4F,  PUSHJ_4B,
@@ -320,7 +356,13 @@ const relax_typeS mmix_relax_table[] =
                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}
+   {0, 0, 0, 0}, {0, 0, 0, 0},
+
+   /* PUSHJSTUB (6, 0).  PUSHJ (3, 0) uses the range, so we set it to infinite.  */
+   {PUSHJSTUB_MAX, PUSHJSTUB_MIN,
+               0,                      ENCODE_RELAX (STATE_PUSHJ, STATE_MAX)},
+   /* PUSHJSTUB (6, 1) isn't used.  */
+   {0, 0,      PUSHJ_MAX_LEN,          0}
 };
 
 const pseudo_typeS md_pseudo_table[] =
@@ -337,10 +379,6 @@ const pseudo_typeS md_pseudo_table[] =
    /* 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}
  };
 
@@ -357,17 +395,15 @@ const char line_comment_chars[] = "*#";
 
 const char line_separator_chars[] = ";";
 
-const char mmix_exp_chars[] = "eE";
+const char EXP_CHARS[] = "eE";
 
-const char mmix_flt_chars[] = "rf";
+const char 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;
+mmix_set_geta_branch_offset (char *opcodep, offsetT value)
 {
   if (value < 0)
     {
@@ -382,9 +418,7 @@ mmix_set_geta_branch_offset (opcodep, value)
 /* Fill in the offset-related part of JMP.  */
 
 static void
-mmix_set_jmp_offset (opcodep, value)
-     char *opcodep;
-     offsetT value;
+mmix_set_jmp_offset (char *opcodep, offsetT value)
 {
   if (value < 0)
     {
@@ -399,22 +433,18 @@ mmix_set_jmp_offset (opcodep, value)
 /* Fill in NOP:s for the expanded part of GETA/JMP/Bcc/PUSHJ.  */
 
 static void
-mmix_fill_nops (opcodep, n)
-     char *opcodep;
-     int n;
+mmix_fill_nops (char *opcodep, int n)
 {
   int i;
 
   for (i = 0; i < n; i++)
-    md_number_to_chars (opcodep + i*4, SWYM_INSN_BYTE << 24, 4);
+    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;
+mmix_current_location (void (*fn) (expressionS *), expressionS *exp)
 {
   (*fn) (exp);
 
@@ -425,10 +455,7 @@ mmix_current_location (fn, exp)
    General idea and code stolen from the tic80 port.  */
 
 static int
-get_operands (max_operands, s, exp)
-     int max_operands;
-     char *s;
-     expressionS exp[];
+get_operands (int max_operands, char *s, expressionS *exp)
 {
   char *p = s;
   int numexp = 0;
@@ -454,7 +481,7 @@ get_operands (max_operands, s, exp)
          return 0;
        }
 
-      /* Begin operand parsing at the current scan point. */
+      /* Begin operand parsing at the current scan point.  */
 
       input_line_pointer = p;
       expression (&exp[numexp]);
@@ -484,7 +511,7 @@ get_operands (max_operands, s, exp)
       input_line_pointer--;
     }
 
-  /* Mark the end of the valid operands with an illegal expression. */
+  /* Mark the end of the valid operands with an illegal expression.  */
   exp[numexp].X_op = O_illegal;
 
   return (numexp);
@@ -494,8 +521,7 @@ get_operands (max_operands, s, exp)
    one.  NAME is a null-terminated string.  */
 
 static int
-get_spec_regno (name)
-     char *name;
+get_spec_regno (char *name)
 {
   int i;
 
@@ -517,10 +543,8 @@ get_spec_regno (name)
 /* 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[];
+get_putget_operands (struct mmix_opcode *insn, char *operands,
+                    expressionS *exp)
 {
   expressionS *expp_reg;
   expressionS *expp_sreg;
@@ -536,6 +560,11 @@ get_putget_operands (insn, operands, exp)
 
   input_line_pointer = p;
 
+  /* Initialize both possible operands to error state, in case we never
+     get further.  */
+  exp[0].X_op = O_illegal;
+  exp[1].X_op = O_illegal;
+
   if (insn->operands == mmix_operands_get)
     {
       expp_reg = &exp[0];
@@ -558,8 +587,10 @@ get_putget_operands (insn, operands, exp)
            p++;
          sregp = p;
          input_line_pointer = sregp;
-         c = get_symbol_end ();
+         c = get_symbol_name (&sregp);
          sregend = input_line_pointer;
+         if (c == '"')
+           ++ input_line_pointer;
        }
     }
   else
@@ -567,14 +598,10 @@ get_putget_operands (insn, operands, exp)
       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;
+      c = get_symbol_name (&sregp);
+      sregend = input_line_pointer;
+      restore_line_pointer (c);
+      p = input_line_pointer;
 
       /* Skip whitespace */
       while (*p == ' ' || *p == '\t')
@@ -613,14 +640,13 @@ get_putget_operands (insn, operands, exp)
 /* Handle MMIX-specific option.  */
 
 int
-md_parse_option (c, arg)
-     int c;
-     char *arg ATTRIBUTE_UNUSED;
+md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
 {
   switch (c)
     {
     case 'x':
       warn_on_expansion = 0;
+      allocate_undefined_gregs_in_linker = 1;
       break;
 
     case OPTION_RELAX:
@@ -653,6 +679,14 @@ md_parse_option (c, arg)
       equated_spec_regs = 0;
       break;
 
+    case OPTION_LINKER_ALLOCATED_GREGS:
+      allocate_undefined_gregs_in_linker = 1;
+      break;
+
+    case OPTION_NOPUSHJSTUBS:
+      pushj_stubs = 0;
+      break;
+
     default:
       return 0;
     }
@@ -663,8 +697,7 @@ md_parse_option (c, arg)
 /* Display MMIX-specific help text.  */
 
 void
-md_show_usage (stream)
-     FILE * stream;
+md_show_usage (FILE * stream)
 {
   fprintf (stream, _(" MMIX-specific command line options:\n"));
   fprintf (stream, _("\
@@ -685,18 +718,22 @@ md_show_usage (stream)
   fprintf (stream, _("\
   -no-merge-gregs         Do not merge GREG definitions with nearby values.\n"));
   fprintf (stream, _("\
+  -linker-allocated-gregs If there's no suitable GREG definition for the\
+                          operands of an instruction, let the linker resolve.\n"));
+  fprintf (stream, _("\
   -x                      Do not warn when an operand to GETA, a branch,\n\
                           PUSHJ or JUMP is not known to be within range.\n\
-                          The linker will catch any errors.\n"));
+                          The linker will catch any errors.  Implies\n\
+                          -linker-allocated-gregs."));
 }
 
 /* Step to end of line, but don't step over the end of the line.  */
 
 static void
-mmix_discard_rest_of_line ()
+mmix_discard_rest_of_line (void)
 {
   while (*input_line_pointer
-        && (! is_end_of_line [(unsigned char) *input_line_pointer]
+        && (! is_end_of_line[(unsigned char) *input_line_pointer]
             || TC_EOL_IN_INSN (input_line_pointer)))
     input_line_pointer++;
 }
@@ -706,7 +743,7 @@ mmix_discard_rest_of_line ()
    delimiter).  */
 
 static void
-mmix_handle_rest_of_empty_line ()
+mmix_handle_rest_of_empty_line (void)
 {
   if (mmix_gnu_syntax)
     demand_empty_rest_of_line ();
@@ -720,7 +757,7 @@ mmix_handle_rest_of_empty_line ()
 /* Initialize GAS MMIX specifics.  */
 
 void
-mmix_md_begin ()
+mmix_md_begin (void)
 {
   int i;
   const struct mmix_opcode *opcode;
@@ -730,26 +767,26 @@ mmix_md_begin ()
 
   /* 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);
+  lex_type[':'] = (LEX_NAME | LEX_BEGIN_NAME);
 
-  mmix_opcode_hash = hash_new ();
+  mmix_opcode_hash = str_htab_create ();
 
   real_reg_section
     = bfd_make_section_old_way (stdoutput, MMIX_REG_SECTION_NAME);
 
   for (opcode = mmix_opcodes; opcode->name; opcode++)
-    hash_insert (mmix_opcode_hash, opcode->name, (char *) opcode);
+    str_hash_insert (mmix_opcode_hash, opcode->name, opcode, 0);
 
   /* We always insert the ordinary registers 0..255 as registers.  */
   for (i = 0; i < 256; i++)
     {
-      char buf[5];
+      char buf[16];
 
       /* Alternatively, we could diddle with '$' and the following number,
         but keeping the registers as symbols helps keep parsing simple.  */
       sprintf (buf, "$%d", i);
-      symbol_table_insert (symbol_new (buf, reg_section, i,
-                                      &zero_address_frag));
+      symbol_table_insert (symbol_new (buf, reg_section,
+                                      &zero_address_frag, i));
     }
 
   /* Insert mmixal built-in names if allowed.  */
@@ -758,29 +795,28 @@ mmix_md_begin ()
       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));
+                                        &zero_address_frag,
+                                        mmix_spec_regs[i].number + 256));
 
       /* FIXME: Perhaps these should be recognized as specials; as field
         names for those instructions.  */
-      symbol_table_insert (symbol_new ("ROUND_CURRENT", reg_section, 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));
+      symbol_table_insert (symbol_new ("ROUND_CURRENT", reg_section,
+                                      &zero_address_frag, 512));
+      symbol_table_insert (symbol_new ("ROUND_OFF", reg_section,
+                                      &zero_address_frag, 512 + 1));
+      symbol_table_insert (symbol_new ("ROUND_UP", reg_section,
+                                      &zero_address_frag, 512 + 2));
+      symbol_table_insert (symbol_new ("ROUND_DOWN", reg_section,
+                                      &zero_address_frag, 512 + 3));
+      symbol_table_insert (symbol_new ("ROUND_NEAR", reg_section,
+                                      &zero_address_frag, 512 + 4));
     }
 }
 
 /* Assemble one insn in STR.  */
 
 void
-md_assemble (str)
-     char *str;
+md_assemble (char *str)
 {
   char *operands = str;
   char modified_char = 0;
@@ -807,7 +843,7 @@ md_assemble (str)
       *operands++ = '\0';
     }
 
-  instruction = (struct mmix_opcode *) hash_find (mmix_opcode_hash, str);
+  instruction = (struct mmix_opcode *) str_hash_find (mmix_opcode_hash, str);
   if (instruction == NULL)
     {
       as_bad (_("unknown opcode: `%s'"), str);
@@ -1031,10 +1067,10 @@ md_assemble (str)
       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.  */
+  /* We also assume that the length of the instruction is at least 4, the
+     size of an unexpanded instruction.  We need a self-contained frag
+     since we want the relocation to point to the instruction, not the
+     variant part.  */
 
   opcodep = frag_more (4);
   mmix_opcode_frag = opc_fragP = frag_now;
@@ -1065,7 +1101,7 @@ md_assemble (str)
        }
 
       if (expand_op)
-       frag_var (rs_machine_dependent, 4*4, 0,
+       frag_var (rs_machine_dependent, 4 * 4, 0,
                  ENCODE_RELAX (STATE_JMP, STATE_UNDF),
                  exp[0].X_add_symbol,
                  exp[0].X_add_number,
@@ -1265,7 +1301,7 @@ md_assemble (str)
       /* SYNCD: "X,$Y,$Z|Z".  */
       /* FALLTHROUGH.  */
     case mmix_operands_regs:
-      /* Three registers, $X,$Y,$Z. */
+      /* Three registers, $X,$Y,$Z.  */
       /* FALLTHROUGH.  */
     case mmix_operands_regs_z:
       /* Operands "$X,$Y,$Z|Z", number of arguments checked above.  */
@@ -1296,6 +1332,7 @@ md_assemble (str)
       if (n_operands == 2)
        {
          symbolS *sym;
+         fixS *tmpfixP;
 
          /* The last operand is immediate whenever we see just two
             operands.  */
@@ -1338,11 +1375,19 @@ md_assemble (str)
             pass expressions as symbols and use fix_new, not fix_new_exp.  */
          sym = make_expr_symbol (exp + 1);
 
+         /* Mark the symbol as being OK for a reloc.  */
+         symbol_get_bfdsym (sym)->flags |= BSF_KEEP;
+
          /* Now we know it can be a "base address plus offset".  Add
             proper fixup types so we can handle this later, when we've
             parsed everything.  */
-         fix_new (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
-                  8, sym, 0, 0, BFD_RELOC_MMIX_BASE_PLUS_OFFSET);
+         tmpfixP
+           = fix_new (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
+                      1, sym, 0, 0, BFD_RELOC_MMIX_BASE_PLUS_OFFSET);
+         /* This is a non-trivial fixup: the ->fx_offset will not
+            reflect the stored value, so the generic overflow test
+            doesn't apply. */
+         tmpfixP->fx_no_overflow = 1;
          break;
        }
 
@@ -1403,7 +1448,7 @@ md_assemble (str)
       break;
 
     case mmix_operands_jmp:
-      /* A JMP.  Everyhing is already done.  */
+      /* A JMP.  Everything is already done.  */
       break;
 
     case mmix_operands_roundregs:
@@ -1438,8 +1483,8 @@ md_assemble (str)
                && ((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_add_number < 0
+                           || exp[1].X_add_number > 4))
                    || (exp[1].X_op != O_register
                        && exp[1].X_op != O_constant))))
          {
@@ -1470,27 +1515,27 @@ md_assemble (str)
 
     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 (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;
+      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.  */
@@ -1544,7 +1589,7 @@ md_assemble (str)
       break;
 
     case mmix_operands_regaddr:
-      /* A GETA/branch-type. */
+      /* A GETA/branch-type.  */
       break;
 
     case mmix_operands_get:
@@ -1622,7 +1667,7 @@ md_assemble (str)
          break;
        }
 
-      /* "0,$Z"; UNSAVE. */
+      /* "0,$Z"; UNSAVE.  */
       if (n_operands != 2
          || exp[0].X_op != O_constant
          || exp[0].X_add_number != 0
@@ -1643,7 +1688,10 @@ md_assemble (str)
       break;
 
     case mmix_operands_xyz_opt:
-      /* SWYM, TRIP, TRAP: zero, one, two or three operands.  */
+      /* SWYM, TRIP, TRAP: zero, one, two or three operands.  It's
+        unspecified whether operands are registers or constants, but
+        when we find register syntax, we require operands to be literal and
+        within 0..255.  */
       if (n_operands == 0 && ! mmix_gnu_syntax)
        /* Zeros are in place - nothing needs to be done for zero
           operands.  We don't allow this in GNU syntax mode, because it
@@ -1654,7 +1702,7 @@ md_assemble (str)
        {
          if (exp[0].X_op == O_constant)
            {
-             if (exp[0].X_add_number > 255*255*255
+             if (exp[0].X_add_number > 255*256*256
                  || exp[0].X_add_number < 0)
                {
                  as_bad (_("invalid operands to opcode %s: `%s'"),
@@ -1696,7 +1744,7 @@ md_assemble (str)
 
          if (exp[1].X_op == O_constant)
            {
-             if (exp[1].X_add_number > 255*255
+             if (exp[1].X_add_number > 255*256
                  || exp[1].X_add_number < 0)
                {
                  as_bad (_("invalid operands to opcode %s: `%s'"),
@@ -1768,62 +1816,64 @@ md_assemble (str)
            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))
+      else
        {
-         /* The meaning of operands to TRIP and TRAP are not defined, so
-            we add combinations not handled above here as we find them.  */
+         /* We can't get here for other cases.  */
+         gas_assert (n_operands <= 3);
+
+         /* The meaning of operands to TRIP and TRAP is not defined (and
+            SWYM operands aren't enforced in mmixal, so let's avoid
+            that).  We add combinations not handled above here as we find
+            them and as they're reported.  */
          if (n_operands == 3)
            {
              /* Don't require non-register operands.  Always generate
                 fixups, so we don't have to copy lots of code and create
-                maintanance problems.  TRIP is supposed to be a rare
+                maintenance problems.  TRIP is supposed to be a rare
                 instruction, so the overhead should not matter.  We
                 aren't allowed to fix_new_exp for an expression which is
-                an  O_register at this point, however.  */
+                an O_register at this point, however.
+
+                Don't use BFD_RELOC_MMIX_REG_OR_BYTE as that modifies
+                the insn for a register in the Z field and we want
+                consistency.  */
              if (exp[0].X_op == O_register)
                opcodep[1] = exp[0].X_add_number;
              else
                fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
-                            1, exp, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
+                            1, exp, 0, BFD_RELOC_8);
              if (exp[1].X_op == O_register)
                opcodep[2] = exp[1].X_add_number;
              else
                fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
-                            1, exp + 1, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
+                            1, exp + 1, 0, BFD_RELOC_8);
              if (exp[2].X_op == O_register)
                opcodep[3] = exp[2].X_add_number;
              else
                fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
-                            1, exp + 2, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
+                            1, exp + 2, 0, BFD_RELOC_8);
            }
          else if (n_operands == 2)
            {
              if (exp[0].X_op == O_register)
-               opcodep[2] = exp[0].X_add_number;
+               opcodep[1] = 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);
+               fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 1,
+                            1, exp, 0, BFD_RELOC_8);
              if (exp[1].X_op == O_register)
                opcodep[3] = exp[1].X_add_number;
              else
-               fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 3,
-                            1, exp + 1, 0, BFD_RELOC_MMIX_REG_OR_BYTE);
+               fix_new_exp (opc_fragP, opcodep - opc_fragP->fr_literal + 2,
+                            2, exp + 1, 0, BFD_RELOC_16);
            }
          else
            {
-             as_bad (_("unsupported operands to %s: `%s'"),
-                     instruction->name, operands);
-             return;
+             /* We can't get here for other cases.  */
+             gas_assert (n_operands == 1 && exp[0].X_op == O_register);
+
+             opcodep[3] = exp[0].X_add_number;
            }
        }
-      else
-       {
-         as_bad (_("invalid operands to opcode %s: `%s'"),
-                 instruction->name, operands);
-         return;
-       }
       break;
 
     case mmix_operands_resume:
@@ -1861,8 +1911,7 @@ md_assemble (str)
    tc_unrecognized_line too, through this function.  */
 
 int
-mmix_assemble_return_nonzero (str)
-     char  *str;
+mmix_assemble_return_nonzero (char *str)
 {
   int last_error_count = had_errors ();
   char *s2 = str;
@@ -1891,19 +1940,16 @@ mmix_assemble_return_nonzero (str)
 /* The PREFIX pseudo.  */
 
 static void
-s_prefix (unused)
-     int unused ATTRIBUTE_UNUSED;
+s_prefix (int unused ATTRIBUTE_UNUSED)
 {
   char *p;
   int c;
 
   SKIP_WHITESPACE ();
 
-  p = input_line_pointer;
-
-  c = get_symbol_end ();
-
-  /* Reseting prefix?  */
+  c = get_symbol_name (&p);
+  
+  /* Resetting prefix?  */
   if (*p == ':' && p[1] == 0)
     mmix_current_prefix = NULL;
   else
@@ -1921,7 +1967,7 @@ s_prefix (unused)
       mmix_current_prefix = p;
     }
 
-  *input_line_pointer = c;
+  (void) restore_line_pointer (c);
 
   mmix_handle_rest_of_empty_line ();
 }
@@ -1934,8 +1980,7 @@ s_prefix (unused)
    that.  (It might be worth a rewrite for other reasons, though).  */
 
 char *
-mmix_prefix_name (shortname)
-     char *shortname;
+mmix_prefix_name (char *shortname)
 {
   if (*shortname == ':')
     return shortname + 1;
@@ -1959,14 +2004,14 @@ mmix_prefix_name (shortname)
    be persistent, perhaps allocated on an obstack.  */
 
 static void
-mmix_greg_internal (label)
-     char *label;
+mmix_greg_internal (char *label)
 {
   expressionS *expP = &mmix_raw_gregs[n_of_raw_gregs].exp;
+  segT section;
 
   /* Don't set the section to register contents section before the
      expression has been parsed; it may refer to the current position.  */
-  expression (expP);
+  section = expression (expP);
 
   /* FIXME: Check that no expression refers to the register contents
      section.  May need to be done in elf64-mmix.c.  */
@@ -1980,6 +2025,24 @@ mmix_greg_internal (label)
       expP->X_op_symbol = NULL;
     }
 
+  if (section == undefined_section)
+    {
+      /* This is an error or a LOC with an expression involving
+        forward references.  For the expression to be correctly
+        evaluated, we need to force a proper symbol; gas loses track
+        of the segment for "local symbols".  */
+      if (expP->X_op == O_add)
+       {
+         symbol_get_value_expression (expP->X_op_symbol);
+         symbol_get_value_expression (expP->X_add_symbol);
+       }
+      else
+       {
+         gas_assert (expP->X_op == O_symbol);
+         symbol_get_value_expression (expP->X_add_symbol);
+       }
+    }
+
   /* We must handle prefixes here, as we save the labels and expressions
      to be output later.  */
   mmix_raw_gregs[n_of_raw_gregs].label
@@ -1996,20 +2059,21 @@ mmix_greg_internal (label)
 /* The ".greg label,expr" worker.  */
 
 static void
-s_greg (unused)
-     int unused ATTRIBUTE_UNUSED;
+s_greg (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 ();
+  c = get_symbol_name (&p);
+
+  if (c == '"')
+    c = * ++ input_line_pointer;
 
-  if (! is_end_of_line [(unsigned char) c])
+  if (! is_end_of_line[(unsigned char) c])
     input_line_pointer++;
 
   if (*p)
@@ -2026,8 +2090,7 @@ s_greg (unused)
 /* The "BSPEC expr" worker.  */
 
 static void
-s_bspec (unused)
-     int unused ATTRIBUTE_UNUSED;
+s_bspec (int unused ATTRIBUTE_UNUSED)
 {
   asection *expsec;
   asection *sec;
@@ -2073,9 +2136,8 @@ s_bspec (unused)
       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))
+      if (!bfd_set_section_flags (sec,
+                                 bfd_section_flags (sec) | SEC_READONLY))
        as_fatal (_("can't set section flags for section %s"), newsecname);
     }
 
@@ -2084,7 +2146,7 @@ s_bspec (unused)
   subseg_set (sec, 0);
 
   /* Save position for missing ESPEC.  */
-  as_where (&bspec_file, &bspec_line);
+  bspec_file = as_where (&bspec_line);
 
   doing_bspec = 1;
 }
@@ -2092,8 +2154,7 @@ s_bspec (unused)
 /* The "ESPEC" worker.  */
 
 static void
-s_espec (unused)
-     int unused ATTRIBUTE_UNUSED;
+s_espec (int unused ATTRIBUTE_UNUSED)
 {
   /* First, check that we *do* have a BSPEC in progress.  */
   if (! doing_bspec)
@@ -2116,8 +2177,7 @@ s_espec (unused)
    Implementing this by means of contents in a section lost.  */
 
 static void
-mmix_s_local (unused)
-     int unused ATTRIBUTE_UNUSED;
+mmix_s_local (int unused ATTRIBUTE_UNUSED)
 {
   expressionS exp;
 
@@ -2147,16 +2207,15 @@ mmix_s_local (unused)
    function may be called multiple times.  */
 
 int
-md_estimate_size_before_relax (fragP, segment)
-     fragS *fragP;
-     segT    segment;
+md_estimate_size_before_relax (fragS *fragP, segT segment)
 {
   int length;
 
 #define HANDLE_RELAXABLE(state)                                                \
  case ENCODE_RELAX (state, STATE_UNDF):                                        \
    if (fragP->fr_symbol != NULL                                                \
-       && S_GET_SEGMENT (fragP->fr_symbol) == segment)                 \
+       && S_GET_SEGMENT (fragP->fr_symbol) == segment                  \
+       && !S_IS_WEAK (fragP->fr_symbol))                               \
      {                                                                 \
        /* The symbol lies in the same segment - a relaxable case.  */  \
        fragP->fr_subtype                                               \
@@ -2168,12 +2227,27 @@ md_estimate_size_before_relax (fragP, segment)
     {
       HANDLE_RELAXABLE (STATE_GETA);
       HANDLE_RELAXABLE (STATE_BCC);
-      HANDLE_RELAXABLE (STATE_PUSHJ);
       HANDLE_RELAXABLE (STATE_JMP);
 
+    case ENCODE_RELAX (STATE_PUSHJ, STATE_UNDF):
+      if (fragP->fr_symbol != NULL
+         && S_GET_SEGMENT (fragP->fr_symbol) == segment
+         && !S_IS_WEAK (fragP->fr_symbol))
+       /* The symbol lies in the same segment - a relaxable case.  */
+       fragP->fr_subtype = ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO);
+      else if (pushj_stubs)
+       /* If we're to generate stubs, assume we can reach a stub after
+           the section.  */
+       fragP->fr_subtype = ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO);
+      /* FALLTHROUGH.  */
+    case ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO):
+    case ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO):
+      /* We need to distinguish different relaxation rounds.  */
+      seg_info (segment)->tc_segment_info_data.last_stubfrag = fragP;
+      break;
+
     case ENCODE_RELAX (STATE_GETA, STATE_ZERO):
     case ENCODE_RELAX (STATE_BCC, STATE_ZERO):
-    case ENCODE_RELAX (STATE_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.  */
@@ -2203,59 +2277,26 @@ md_estimate_size_before_relax (fragP, segment)
    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;
+const char *
+md_atof (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;
+  if (type == 'r')
+    type = 'f';
+  /* FIXME: Having 'f' in FLT_CHARS (and here) makes it
+     problematic to also have a forward reference in an expression.
+     The testsuite wants it, and it's customary.
+     We'll deal with the real problems when they come; we share the
+     problem with most other ports.  */
+  return ieee_md_atof (type, litP, sizeP, TRUE);
 }
 
 /* Convert variable-sized frags into one or more fixups.  */
 
 void
-md_convert_frag (abfd, sec, fragP)
-     bfd *abfd ATTRIBUTE_UNUSED;
-     segT sec ATTRIBUTE_UNUSED;
-     fragS *fragP;
+md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT sec ATTRIBUTE_UNUSED,
+                fragS *fragP)
 {
-  /* Pointer to first byte in variable-sized part of the frag. */
+  /* Pointer to first byte in variable-sized part of the frag.  */
   char *var_partp;
 
   /* Pointer to first opcode byte in frag.  */
@@ -2293,48 +2334,69 @@ md_convert_frag (abfd, sec, fragP)
   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_PUSHJSTUB, STATE_ZERO):
+      /* Setting the unknown bits to 0 seems the most appropriate.  */
+      mmix_set_geta_branch_offset (opcodep, 0);
+      tmpfixP = fix_new (opc_fragP, opcodep - opc_fragP->fr_literal, 4,
+                        fragP->fr_symbol, fragP->fr_offset, 1,
+                        BFD_RELOC_MMIX_PUSHJ_STUBBABLE);
+      COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
+      var_part_size = 0;
 
-  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;
+      /* This is a non-trivial fixup; we'll be calling a generated
+        stub, whose address fits into the fixup.  The actual target,
+        as reflected by the fixup value, is further away than fits
+        into the fixup, so the generic overflow test doesn't
+        apply. */
+      tmpfixP->fx_no_overflow = 1;
+      break;
 
-  case 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
+    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;
+      break;
+
+    case STATE_GREG_DEF:
+      if (fragP->tc_frag_data == NULL)
+       {
+         /* We must initialize data that's supposed to be "fixed up" to
+            avoid emitting garbage, because md_apply_fix won't do
+            anything for undefined symbols.  */
+         md_number_to_chars (var_partp, 0, 8);
+         tmpfixP
+           = fix_new (fragP, var_partp - fragP->fr_literal, 8,
+                      fragP->fr_symbol, fragP->fr_offset, 0, BFD_RELOC_64);
+         COPY_FR_WHERE_TO_FX (fragP, tmpfixP);
+         mmix_gregs[n_of_cooked_gregs++] = tmpfixP;
+         var_part_size = 8;
+       }
+      else
+       var_part_size = 0;
+      break;
 
 #define HANDLE_MAX_RELOC(state, reloc)                                 \
   case ENCODE_RELAX (state, STATE_MAX):                                        \
@@ -2349,15 +2411,15 @@ md_convert_frag (abfd, sec, fragP)
     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);
+      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;
-  }
+    default:
+      BAD_CASE (fragP->fr_subtype);
+      break;
+    }
 
   fragP->fr_fix += var_part_size;
   fragP->fr_var = 0;
@@ -2369,15 +2431,12 @@ md_convert_frag (abfd, sec, fragP)
 
    Note that this function isn't called when linkrelax != 0.  */
 
-int
-md_apply_fix3 (fixP, valp, segment)
-     fixS *   fixP;
-     valueT * valp;
-     segT     segment;
+void
+md_apply_fix (fixS *fixP, valueT *valP, segT segment)
 {
   char *buf  = fixP->fx_where + fixP->fx_frag->fr_literal;
   /* Note: use offsetT because it is signed, valueT is unsigned.  */
-  offsetT val  = (offsetT) * valp;
+  offsetT val  = (offsetT) * valP;
   segT symsec
     = (fixP->fx_addsy == NULL
        ? absolute_section : S_GET_SEGMENT (fixP->fx_addsy));
@@ -2393,11 +2452,10 @@ md_apply_fix3 (fixP, valp, segment)
              && 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)))))
+                 || symsec != reg_section))))
     {
       fixP->fx_done = 0;
-      return 0;
+      return;
     }
   else if (fixP->fx_r_type == BFD_RELOC_MMIX_LOCAL
           || fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
@@ -2405,7 +2463,7 @@ md_apply_fix3 (fixP, valp, segment)
     {
       /* These are never "fixed".  */
       fixP->fx_done = 0;
-      return 0;
+      return;
     }
   else
     /* We assume every other relocation is "fixed".  */
@@ -2437,6 +2495,7 @@ md_apply_fix3 (fixP, valp, segment)
     case BFD_RELOC_MMIX_GETA:
     case BFD_RELOC_MMIX_CBRANCH:
     case BFD_RELOC_MMIX_PUSHJ:
+    case BFD_RELOC_MMIX_PUSHJ_STUBBABLE:
       /* If this fixup is out of range, punt to the linker to emit an
         error.  This should only happen with -no-expand.  */
       if (val < -(((offsetT) 1 << 19)/2)
@@ -2478,11 +2537,17 @@ md_apply_fix3 (fixP, valp, segment)
 
     case BFD_RELOC_MMIX_REG_OR_BYTE:
       if (fixP->fx_addsy != NULL
-         && (S_GET_SEGMENT (fixP->fx_addsy) != real_reg_section
+         && (S_GET_SEGMENT (fixP->fx_addsy) != reg_section
              || S_GET_VALUE (fixP->fx_addsy) > 255)
          && S_GET_SEGMENT (fixP->fx_addsy) != absolute_section)
-       as_bad_where (fixP->fx_file, fixP->fx_line,
-                     _("invalid operands"));
+       {
+         as_bad_where (fixP->fx_file, fixP->fx_line,
+                       _("invalid operands"));
+         /* We don't want this "symbol" appearing in output, because
+            that will fail.  */
+         fixP->fx_done = 1;
+       }
+
       buf[0] = val;
 
       /* If this reloc is for a Z field, we need to adjust
@@ -2493,31 +2558,25 @@ md_apply_fix3 (fixP, valp, segment)
          && (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_SEGMENT (fixP->fx_addsy) != reg_section
          || S_GET_VALUE (fixP->fx_addsy) > 255)
-       as_bad_where (fixP->fx_file, fixP->fx_line,
-                     _("invalid operands"));
-      *buf = val;
+       {
+         as_bad_where (fixP->fx_file, fixP->fx_line,
+                       _("invalid operands"));
+         fixP->fx_done = 1;
+       }
 
-      if (fixP->fx_addsy
-         && S_GET_SEGMENT (fixP->fx_addsy) == real_reg_section)
-       symbol_clear_used_in_reloc (fixP->fx_addsy);
+      *buf = val;
       break;
 
     case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
       /* These are never "fixed".  */
       fixP->fx_done = 0;
-      return 0;
+      return;
 
     case BFD_RELOC_MMIX_PUSHJ_1:
     case BFD_RELOC_MMIX_PUSHJ_2:
@@ -2541,17 +2600,13 @@ md_apply_fix3 (fixP, valp, segment)
     /* 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;
+cmp_greg_val_greg_symbol_fixes (const void *p1, const void *p2)
 {
   offsetT val1 = *(offsetT *) p1;
   offsetT val2 = ((struct mmix_symbol_greg_fixes *) p2)->offs;
@@ -2568,19 +2623,23 @@ cmp_greg_val_greg_symbol_fixes (p1, p2)
 /* Generate a machine-dependent relocation.  */
 
 arelent *
-tc_gen_reloc (section, fixP)
-     asection *section ATTRIBUTE_UNUSED;
-     fixS *fixP;
+tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixP)
 {
   bfd_signed_vma val
-    = fixP->fx_offset + (fixP->fx_addsy ? S_GET_VALUE (fixP->fx_addsy) : 0);
+    = fixP->fx_offset
+    + (fixP->fx_addsy != NULL
+       && !S_IS_WEAK (fixP->fx_addsy)
+       && !S_IS_COMMON (fixP->fx_addsy)
+       ? S_GET_VALUE (fixP->fx_addsy) : 0);
   arelent *relP;
   bfd_reloc_code_real_type code = BFD_RELOC_NONE;
   char *buf  = fixP->fx_where + fixP->fx_frag->fr_literal;
   symbolS *addsy = fixP->fx_addsy;
   asection *addsec = addsy == NULL ? NULL : S_GET_SEGMENT (addsy);
-  bfd_vma addend = fixP->fx_offset;
   asymbol *baddsy = addsy != NULL ? symbol_get_bfdsym (addsy) : NULL;
+  bfd_vma addend
+    = val - (baddsy == NULL || S_IS_COMMON (addsy) || S_IS_WEAK (addsy)
+            ? 0 : bfd_asymbol_value (baddsy));
 
   /* A single " LOCAL expression" in the wrong section will not work when
      linking to MMO; relocations for zero-content sections are then
@@ -2591,7 +2650,7 @@ tc_gen_reloc (section, fixP)
      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)
+  if (bfd_section_size (section) == 0)
     as_bad_where
       (fixP->fx_file, fixP->fx_line,
        fixP->fx_r_type == BFD_RELOC_MMIX_LOCAL
@@ -2610,10 +2669,9 @@ tc_gen_reloc (section, fixP)
     case BFD_RELOC_8:
       code = fixP->fx_r_type;
 
-      if (addsy == NULL
-         || bfd_is_abs_section (S_GET_SEGMENT (addsy)))
+      if (addsy == NULL || bfd_is_abs_section (addsec))
        {
-         /* Resolve this reloc now, as md_apply_fix3 would have done (not
+         /* Resolve this reloc now, as md_apply_fix would have done (not
             called if -linkrelax).  There is no point in keeping a reloc
             to an absolute symbol.  No reloc that is subject to
             relaxation must be to an absolute symbol; difference
@@ -2646,6 +2704,7 @@ tc_gen_reloc (section, fixP)
     case BFD_RELOC_MMIX_PUSHJ_1:
     case BFD_RELOC_MMIX_PUSHJ_2:
     case BFD_RELOC_MMIX_PUSHJ_3:
+    case BFD_RELOC_MMIX_PUSHJ_STUBBABLE:
     case BFD_RELOC_MMIX_JMP:
     case BFD_RELOC_MMIX_JMP_1:
     case BFD_RELOC_MMIX_JMP_2:
@@ -2660,8 +2719,8 @@ tc_gen_reloc (section, fixP)
         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),
+         && (bfd_is_und_section (addsec)
+             || strcmp (bfd_section_name (addsec),
                         MMIX_REG_CONTENTS_SECTION_NAME) == 0))
        {
          code = fixP->fx_r_type;
@@ -2674,14 +2733,13 @@ tc_gen_reloc (section, fixP)
          && (S_GET_SEGMENT (addsy) != real_reg_section
              || val > 255
              || val < 0)
-         && ! bfd_is_abs_section (S_GET_SEGMENT (addsy)))
+         && ! bfd_is_abs_section (addsec))
        goto badop;
 
       /* Set the "immediate" bit of the insn if this relocation is to Z
         field when the value is a numeric value, i.e. not a register.  */
       if ((fixP->fx_where & 3) == 3
-         && (addsy == NULL
-             || S_GET_SEGMENT (addsy) == absolute_section))
+         && (addsy == NULL || bfd_is_abs_section (addsec)))
        buf[-3] |= IMM_OFFSET_BIT;
 
       buf[0] = val;
@@ -2689,8 +2747,8 @@ tc_gen_reloc (section, fixP)
 
     case BFD_RELOC_MMIX_BASE_PLUS_OFFSET:
       if (addsy != NULL
-         &&  strcmp (bfd_get_section_name (addsec->owner, addsec),
-                     MMIX_REG_CONTENTS_SECTION_NAME) == 0)
+         && strcmp (bfd_section_name (addsec),
+                    MMIX_REG_CONTENTS_SECTION_NAME) == 0)
        {
          /* This changed into a register; the relocation is for the
             register-contents section.  The constant part remains zero.  */
@@ -2703,16 +2761,16 @@ tc_gen_reloc (section, fixP)
 
         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)
+      if (addsy == NULL || addsec != real_reg_section)
        {
          struct mmix_symbol_gregs *gregs;
          struct mmix_symbol_greg_fixes *fix;
 
-         if (S_IS_DEFINED (addsy))
+         if (S_IS_DEFINED (addsy)
+             && !bfd_is_com_section (addsec)
+             && !S_IS_WEAK (addsy))
            {
-             if (! symbol_section_p (addsy)
-                 && ! bfd_is_abs_section (S_GET_SEGMENT (addsy)))
+             if (! symbol_section_p (addsy) && ! bfd_is_abs_section (addsec))
                as_fatal (_("internal: BFD_RELOC_MMIX_BASE_PLUS_OFFSET not resolved to section"));
 
              /* If this is an absolute symbol sufficiently near
@@ -2722,7 +2780,7 @@ tc_gen_reloc (section, fixP)
                 comparisons.  */
              if (lowest_data_loc != (bfd_vma) -1
                  && (bfd_vma) val + 256 > lowest_data_loc
-                 && bfd_is_abs_section (S_GET_SEGMENT (addsy)))
+                 && bfd_is_abs_section (addsec))
                {
                  val -= (offsetT) lowest_data_loc;
                  addsy = section_symbol (data_section);
@@ -2730,7 +2788,7 @@ tc_gen_reloc (section, fixP)
              /* 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)))
+                      && bfd_is_abs_section (addsec))
                {
                  val -= (offsetT) lowest_text_loc;
                  addsy = section_symbol (text_section);
@@ -2740,8 +2798,7 @@ tc_gen_reloc (section, fixP)
          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.  */
+            we can't do anything.  */
          if (gregs == NULL
              || (fix = bsearch (&val, gregs->greg_fixes, gregs->n_gregs,
                                 sizeof (gregs->greg_fixes[0]),
@@ -2752,8 +2809,17 @@ tc_gen_reloc (section, fixP)
                 before the address we want.  */
              || fix->offs + 255 < val)
            {
-             as_bad_where (fixP->fx_file, fixP->fx_line,
-                           _("no suitable GREG definition for operands"));
+             /* We can either let the linker allocate GREGs
+                automatically, or emit an error.  */
+             if (allocate_undefined_gregs_in_linker)
+               {
+                 /* The values in baddsy and addend are right.  */
+                 code = fixP->fx_r_type;
+                 break;
+               }
+             else
+               as_bad_where (fixP->fx_file, fixP->fx_line,
+                             _("no suitable GREG definition for operands"));
              return NULL;
            }
          else
@@ -2783,8 +2849,8 @@ tc_gen_reloc (section, fixP)
 
     case BFD_RELOC_MMIX_REG:
       if (addsy != NULL
-         && (bfd_is_und_section (S_GET_SEGMENT (addsy))
-             || strcmp (bfd_get_section_name (addsec->owner, addsec),
+         && (bfd_is_und_section (addsec)
+             || strcmp (bfd_section_name (addsec),
                         MMIX_REG_CONTENTS_SECTION_NAME) == 0))
        {
          code = fixP->fx_r_type;
@@ -2792,10 +2858,10 @@ tc_gen_reloc (section, fixP)
        }
 
       if (addsy != NULL
-         && (S_GET_SEGMENT (addsy) != real_reg_section
+         && (addsec != real_reg_section
              || val > 255
              || val < 0)
-         && ! bfd_is_und_section (S_GET_SEGMENT (addsy)))
+         && ! bfd_is_und_section (addsec))
        /* Drop through to error message.  */
        ;
       else
@@ -2803,11 +2869,11 @@ tc_gen_reloc (section, fixP)
          buf[0] = val;
          return NULL;
        }
-      /* FALLTHROUGH. */
+      /* FALLTHROUGH.  */
 
-      /* The others are supposed to be handled by md_apply_fix3.
+      /* The others are supposed to be handled by md_apply_fix.
         FIXME: ... which isn't called when -linkrelax.  Move over
-        md_apply_fix3 code here for everything reasonable.  */
+        md_apply_fix code here for everything reasonable.  */
     badop:
     default:
       as_bad_where
@@ -2816,14 +2882,13 @@ tc_gen_reloc (section, fixP)
 
       /* 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);
+      fixP->fx_addsy = NULL;
       return NULL;
     }
 
-  relP = (arelent *) xmalloc (sizeof (arelent));
-  assert (relP != 0);
-  relP->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
+  relP = XNEW (arelent);
+  gas_assert (relP != 0);
+  relP->sym_ptr_ptr = XNEW (asymbol *);
   *relP->sym_ptr_ptr = baddsy;
   relP->address = fixP->fx_frag->fr_address + fixP->fx_where;
 
@@ -2852,10 +2917,10 @@ tc_gen_reloc (section, fixP)
    ugly labels_without_colons etc.  */
 
 void
-mmix_handle_mmixal ()
+mmix_handle_mmixal (void)
 {
-  char *s0 = input_line_pointer;
-  char *s;
+  char *insn;
+  char *s = input_line_pointer;
   char *label = NULL;
   char c;
 
@@ -2865,93 +2930,96 @@ mmix_handle_mmixal ()
   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]))
+  if (ISDIGIT (s[0]) && s[1] == 'H' && ISSPACE (s[2]))
     {
-      char *s;
-      current_fb_label = input_line_pointer[0] - '0';
+      current_fb_label = s[0] - '0';
 
       /* We have to skip the label, but also preserve the newlineness of
         the previous character, since the caller checks that.  It's a
         mess we blame on the caller.  */
-      input_line_pointer[1] = input_line_pointer[-1];
-      input_line_pointer += 2;
+      s[1] = s[-1];
+      s += 2;
+      input_line_pointer = s;
 
-      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])
+      if (is_end_of_line[(unsigned int) *s])
        {
-         char *name;
          unsigned int line;
-         as_where (&name, &line);
+         const char * name = as_where (&line);
          as_bad_where (name, line + 1,
                        _("[0-9]H labels may not appear alone on a line"));
          current_fb_label = -1;
        }
       if (*s == '.')
        {
-         char *name;
          unsigned int line;
-         as_where (&name, &line);
+         const char * name  = as_where (&line);
          as_bad_where (name, line + 1,
                        _("[0-9]H labels do not mix with dot-pseudos"));
          current_fb_label = -1;
        }
+
+      /* Back off to the last space before the opcode so we don't handle
+        the opcode as a label.  */
+      s--;
     }
   else
+    current_fb_label = -1;
+
+  if (*s == '.')
     {
-      current_fb_label = -1;
-      if (is_name_beginner (input_line_pointer[0]))
-       label = input_line_pointer;
+      /* If the first character is a '.', then it's a pseudodirective, not a
+        label.  Make GAS not handle label-without-colon on this line.  We
+        also don't do mmixal-specific stuff on this line.  */
+      label_without_colon_this_line = 0;
+      return;
     }
 
-  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)
+  if (*s == 0 || is_end_of_line[(unsigned int) *s])
+    /* We avoid handling empty lines here.  */
+    return;
+
+  if (is_name_beginner (*s))
+    label = s;
+
+  /* If there is a label, skip over it.  */
+  while (*s && is_part_of_name (*s))
+    s++;
+
+  /* Find the start of the instruction or pseudo following the label,
+     if there is one.  */
+  for (insn = s;
+       *insn && ISSPACE (*insn) && ! is_end_of_line[(unsigned int) *insn];
+       insn++)
+    /* Empty */
+    ;
+
+  /* Remove a trailing ":" off labels, as they'd otherwise be considered
+     part of the name.  But don't do this for local labels.  */
+  if (s != input_line_pointer && s[-1] == ':'
+      && (s - 2 != input_line_pointer
+         || ! ISDIGIT (s[-2])))
+    s[-1] = ' ';
+  else if (label != NULL
+          /* For a lone label on a line, we don't attach it to the next
+             instruction or MMIXAL-pseudo (getting its alignment).  Thus
+             is acts like a "normal" :-ended label.  Ditto if it's
+             followed by a non-MMIXAL pseudo.  */
+          && !is_end_of_line[(unsigned int) *insn]
+          && *insn != '.')
     {
       /* For labels that don't end in ":", we save it so we can later give
         it the same alignment and address as the associated instruction.  */
 
       /* Make room for the label including the ending nul.  */
-      int len_0 = s0 - label + 1;
+      size_t len_0 = s - label + 1;
 
       /* Save this label on the MMIX symbol obstack.  Saving it on an
         obstack is needless for "IS"-pseudos, but it's harmless and we
@@ -2961,14 +3029,10 @@ mmix_handle_mmixal ()
       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;
+  /* If we have a non-MMIXAL pseudo, we have not business with the rest of
+     the line.  */
+  if (*insn == '.')
+    return;
 
   /* Find local labels of operands.  Look for "[0-9][FB]" where the
      characters before and after are not part of words.  Break if a single
@@ -2980,18 +3044,17 @@ mmix_handle_mmixal ()
 
   /* 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])
+      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;
+  s = insn;
 
   /* Skip the insn.  */
   while (*s
@@ -3021,7 +3084,7 @@ mmix_handle_mmixal ()
          /* FIXME: Test-case for semi-colon in string.  */
          while (*s
                 && *s != '"'
-                && (! is_end_of_line [(unsigned int) *s] || *s == ';'))
+                && (! is_end_of_line[(unsigned int) *s] || *s == ';'))
            s++;
 
          if (*s == '"')
@@ -3031,7 +3094,9 @@ mmix_handle_mmixal ()
        {
          if ((s[1] != 'B' && s[1] != 'F')
              || is_part_of_name (s[-1])
-             || is_part_of_name (s[2]))
+             || is_part_of_name (s[2])
+             /* Don't treat e.g. #1F as a local-label reference.  */
+             || (s != input_line_pointer && s[-1] == '#'))
            s++;
          else
            {
@@ -3057,7 +3122,7 @@ mmix_handle_mmixal ()
 
   /* Make IS into an EQU by replacing it with "= ".  Only match upper-case
      though; let lower-case be a syntax error.  */
-  s = s0;
+  s = insn;
   if (s[0] == 'I' && s[1] == 'S' && ISSPACE (s[2]))
     {
       *s = '=';
@@ -3163,8 +3228,7 @@ mmix_handle_mmixal ()
    We fill in the label as an expression.  */
 
 void
-mmix_fb_label (expP)
-     expressionS *expP;
+mmix_fb_label (expressionS *expP)
 {
   symbolS *sym;
   char *fb_internal_name;
@@ -3213,40 +3277,30 @@ mmix_fb_label (expP)
    relaxing.  */
 
 int
-mmix_force_relocation (fixP)
-     fixS * fixP;
+mmix_force_relocation (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
+  /* All our pcrel relocations are must-keep.  Note that md_apply_fix 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).  */
+     md_apply_fix (or tc_gen_reloc if linkrelax).  */
   if (fixP->fx_pcrel)
     return 1;
 
-  return 0;
+  return generic_force_reloc (fixP);
 }
 
 /* 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;
+md_pcrel_from_section (fixS *fixP, segT sec)
 {
   if (fixP->fx_addsy != (symbolS *) NULL
       && (! S_IS_DEFINED (fixP->fx_addsy)
@@ -3261,47 +3315,29 @@ md_pcrel_from_section (fixP, sec)
 }
 
 /* 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.  */
+   register section.  */
 
 void
-mmix_adjust_symtab ()
+mmix_adjust_symtab (void)
 {
   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))
+  for (sym = symbol_rootP; sym != NULL; sym = symbol_next (sym))
     if (S_GET_SEGMENT (sym) == reg_section)
       {
-       if (sym == regsec
-           || (!S_IS_EXTERN (sym) && !symbol_used_in_reloc_p (sym)))
+       if (sym == regsec)
          {
+           if (S_IS_EXTERNAL (sym) || symbol_used_in_reloc_p (sym))
+             abort ();
            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);
-         }
+         /* 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.  */
+         S_SET_SEGMENT (sym, real_reg_section);
       }
 }
 
@@ -3312,7 +3348,7 @@ mmix_adjust_symtab ()
    thought at the time I first wrote this.  */
 
 int
-mmix_label_without_colon_this_line ()
+mmix_label_without_colon_this_line (void)
 {
   int retval = label_without_colon_this_line;
 
@@ -3328,18 +3364,123 @@ mmix_label_without_colon_this_line ()
    join with.  */
 
 long
-mmix_md_relax_frag (seg, fragP, stretch)
-     segT seg;
-     fragS *fragP;
-     long stretch;
+mmix_md_relax_frag (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);
+  switch (fragP->fr_subtype)
+    {
+      /* Growth for this type has been handled by mmix_md_end and
+        correctly estimated, so there's nothing more to do here.  */
+    case STATE_GREG_DEF:
+      return 0;
 
-  /* If we're defined, we don't grow.  */
-  if (fragP->fr_subtype == STATE_GREG_DEF)
-    return 0;
+    case ENCODE_RELAX (STATE_PUSHJ, STATE_ZERO):
+      {
+       /* We need to handle relaxation type ourselves, since relax_frag
+          doesn't update fr_subtype if there's no size increase in the
+          current section; when going from plain PUSHJ to a stub.  This
+          is otherwise functionally the same as relax_frag in write.c,
+          simplified for this case.  */
+       offsetT aim;
+       addressT target;
+       addressT address;
+       symbolS *symbolP;
+       target = fragP->fr_offset;
+       address = fragP->fr_address;
+       symbolP = fragP->fr_symbol;
+
+       if (symbolP)
+         {
+           fragS *sym_frag;
+
+           sym_frag = symbol_get_frag (symbolP);
+           know (S_GET_SEGMENT (symbolP) != absolute_section
+                 || sym_frag == &zero_address_frag);
+           target += S_GET_VALUE (symbolP);
+
+           /* If frag has yet to be reached on this pass, assume it will
+              move by STRETCH just as we did.  If this is not so, it will
+              be because some frag between grows, and that will force
+              another pass.  */
+
+           if (stretch != 0
+               && sym_frag->relax_marker != fragP->relax_marker
+               && S_GET_SEGMENT (symbolP) == seg)
+             target += stretch;
+         }
+
+       aim = target - address - fragP->fr_fix;
+       if (aim >= PUSHJ_0B && aim <= PUSHJ_0F)
+         {
+           /* Target is reachable with a PUSHJ.  */
+           segment_info_type *seginfo = seg_info (seg);
+
+           /* If we're at the end of a relaxation round, clear the stub
+              counter as initialization for the next round.  */
+           if (fragP == seginfo->tc_segment_info_data.last_stubfrag)
+             seginfo->tc_segment_info_data.nstubs = 0;
+           return 0;
+         }
+
+       /* Not reachable.  Try a stub.  */
+       fragP->fr_subtype = ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO);
+      }
+      /* FALLTHROUGH.  */
+
+      /* See if this PUSHJ is redirectable to a stub.  */
+    case ENCODE_RELAX (STATE_PUSHJSTUB, STATE_ZERO):
+      {
+       segment_info_type *seginfo = seg_info (seg);
+       fragS *lastfrag = seginfo->frchainP->frch_last;
+       relax_substateT prev_type = fragP->fr_subtype;
+
+       /* The last frag is always an empty frag, so it suffices to look
+          at its address to know the ending address of this section.  */
+       know (lastfrag->fr_type == rs_fill
+             && lastfrag->fr_fix == 0
+             && lastfrag->fr_var == 0);
+
+       /* For this PUSHJ to be relaxable into a call to a stub, the
+          distance must be no longer than 256k bytes from the PUSHJ to
+          the end of the section plus the maximum size of stubs so far.  */
+       if ((lastfrag->fr_address
+            + stretch
+            + PUSHJ_MAX_LEN * seginfo->tc_segment_info_data.nstubs)
+           - (fragP->fr_address + fragP->fr_fix)
+           > GETA_0F
+           || !pushj_stubs)
+         fragP->fr_subtype = mmix_relax_table[prev_type].rlx_more;
+       else
+         seginfo->tc_segment_info_data.nstubs++;
+
+       /* If we're at the end of a relaxation round, clear the stub
+          counter as initialization for the next round.  */
+       if (fragP == seginfo->tc_segment_info_data.last_stubfrag)
+         seginfo->tc_segment_info_data.nstubs = 0;
+
+       return
+          (mmix_relax_table[fragP->fr_subtype].rlx_length
+           - mmix_relax_table[prev_type].rlx_length);
+      }
+
+    case ENCODE_RELAX (STATE_PUSHJ, STATE_MAX):
+      {
+       segment_info_type *seginfo = seg_info (seg);
+
+       /* Need to cover all STATE_PUSHJ states to act on the last stub
+          frag (the end of this relax round; initialization for the
+          next).  */
+       if (fragP == seginfo->tc_segment_info_data.last_stubfrag)
+         seginfo->tc_segment_info_data.nstubs = 0;
+
+       return 0;
+      }
+
+    default:
+      return relax_frag (seg, fragP, stretch);
+
+    case STATE_GREG_UNDF:
+      BAD_CASE (fragP->fr_subtype);
+    }
 
   as_fatal (_("internal: unexpected relax type %d:%d"),
            fragP->fr_type, fragP->fr_subtype);
@@ -3349,10 +3490,12 @@ mmix_md_relax_frag (seg, fragP, stretch)
 /* Various things we punt until all input is seen.  */
 
 void
-mmix_md_end ()
+mmix_md_end (void)
 {
   fragS *fragP;
   symbolS *mainsym;
+  asection *regsec;
+  struct loc_assert_s *loc_assert;
   int i;
 
   /* The first frag of GREG:s going into the register contents section.  */
@@ -3377,8 +3520,8 @@ mmix_md_end ()
       sprintf (locsymbol, ":%s%s", MMIX_LOC_SECTION_START_SYMBOL_PREFIX,
               ".text");
       symbolP
-       = symbol_new (locsymbol, absolute_section, lowest_text_loc,
-                     &zero_address_frag);
+       = symbol_new (locsymbol, absolute_section, &zero_address_frag,
+                     lowest_text_loc);
       S_SET_EXTERNAL (symbolP);
     }
 
@@ -3387,14 +3530,14 @@ mmix_md_end ()
     {
       symbolS *symbolP;
       char locsymbol[sizeof (":") - 1
-                   + sizeof (MMIX_LOC_SECTION_START_SYMBOL_PREFIX) - 1
-                   + sizeof (".data")];
+                    + 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);
+       = symbol_new (locsymbol, absolute_section, &zero_address_frag,
+                     lowest_data_loc);
       S_SET_EXTERNAL (symbolP);
     }
 
@@ -3410,6 +3553,38 @@ mmix_md_end ()
       S_SET_EXTERNAL (mainsym);
     }
 
+  /* Check that we didn't LOC into the unknown, or rather that when it
+     was unknown, we actually change sections.  */
+  for (loc_assert = loc_asserts;
+       loc_assert != NULL;
+       loc_assert = loc_assert->next)
+    {
+      segT actual_seg;
+
+      resolve_symbol_value (loc_assert->loc_sym);
+      actual_seg = S_GET_SEGMENT (loc_assert->loc_sym);
+      if (actual_seg != loc_assert->old_seg)
+       {
+         const char *fnam;
+         unsigned int line;
+         int e_valid = expr_symbol_where (loc_assert->loc_sym, &fnam, &line);
+
+         gas_assert (e_valid == 1);
+         as_bad_where (fnam, line,
+                       _("LOC to section unknown or indeterminable "
+                         "at first pass"));
+
+         /* Patch up the generic location data to avoid cascading
+            error messages from later passes.  (See original in
+            write.c:relax_segment.)  */
+         fragP = loc_assert->frag;
+         fragP->fr_type = rs_align;
+         fragP->fr_subtype = 0;
+         fragP->fr_offset = 0;
+         fragP->fr_fix = 0;
+       }
+    }
+
   if (n_of_raw_gregs != 0)
     {
       /* Emit GREGs.  They are collected in order of appearance, but must
@@ -3417,9 +3592,9 @@ mmix_md_end ()
         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);
+
+      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
@@ -3446,6 +3621,11 @@ mmix_md_end ()
       subseg_set (this_segment, this_subsegment);
     }
 
+  regsec = bfd_get_section_by_name (stdoutput, MMIX_REG_CONTENTS_SECTION_NAME);
+  /* Mark the section symbol as being OK for a reloc.  */
+  if (regsec != NULL)
+    regsec->symbol->flags |= BSF_KEEP;
+
   /* Iterate over frags resulting from GREGs and move those that evidently
      have the same value together and point one to another.
 
@@ -3524,9 +3704,7 @@ mmix_md_end ()
 /* qsort function for mmix_symbol_gregs.  */
 
 static int
-cmp_greg_symbol_fixes (parg, qarg)
-     const PTR parg;
-     const PTR qarg;
+cmp_greg_symbol_fixes (const void *parg, const void *qarg)
 {
   const struct mmix_symbol_greg_fixes *p
     = (const struct mmix_symbol_greg_fixes *) parg;
@@ -3543,7 +3721,7 @@ cmp_greg_symbol_fixes (parg, qarg)
    as an ELF section.  */
 
 void
-mmix_frob_file ()
+mmix_frob_file (void)
 {
   int i;
   struct mmix_symbol_gregs *all_greg_symbols[MAX_GREGS];
@@ -3574,7 +3752,9 @@ mmix_frob_file ()
 
       /* 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 (S_IS_DEFINED (sym)
+         && !bfd_is_com_section (S_GET_SEGMENT (sym))
+         && !S_IS_WEAK (sym))
        {
          if (! symbol_section_p (sym)
              && ! bfd_is_abs_section (S_GET_SEGMENT (sym)))
@@ -3607,7 +3787,7 @@ mmix_frob_file ()
 
       if (gregs == NULL)
        {
-         gregs = xmalloc (sizeof (*gregs));
+         gregs = XNEW (struct mmix_symbol_gregs);
          gregs->n_gregs = 0;
          symbol_set_tc (sym, &gregs);
          all_greg_symbols[n_greg_symbols++] = gregs;
@@ -3625,20 +3805,11 @@ mmix_frob_file ()
 
   if (real_reg_section != NULL)
     {
-      asection **secpp;
-
       /* FIXME: Pass error state gracefully.  */
-      if (bfd_get_section_flags (stdoutput, real_reg_section) & SEC_HAS_CONTENTS)
+      if (bfd_section_flags (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;
+      bfd_section_list_remove (stdoutput, real_reg_section);
       --stdoutput->section_count;
     }
 
@@ -3651,12 +3822,10 @@ mmix_frob_file ()
    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;
+mmix_parse_predefined_name (char *name, expressionS *expP)
 {
   char *canon_name;
-  char *handler_charp;
+  const char *handler_charp;
   const char handler_chars[] = "DVWIOUZX";
   symbolS *symp;
 
@@ -3676,9 +3845,8 @@ mmix_parse_predefined_name (name, expP)
         script.  */
       symp = symbol_find (name);
       if (symp == NULL)
-       symp = symbol_new (name, text_section,
-                          0x10 * (handler_charp + 1 - handler_chars),
-                          &zero_address_frag);
+       symp = symbol_new (name, text_section, &zero_address_frag,
+                          0x10 * (handler_charp + 1 - handler_chars));
     }
   else
     {
@@ -3731,14 +3899,14 @@ mmix_parse_predefined_name (name, expP)
        return 0;
 
       for (i = 0;
-          i < sizeof (predefined_abs_syms)/sizeof (predefined_abs_syms[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));
+                                            &zero_address_frag,
+                                            predefined_abs_syms[i].val));
 
            /* Let gas find the symbol we just created, through its
                ordinary lookup.  */
@@ -3757,61 +3925,17 @@ mmix_parse_predefined_name (name, expP)
   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 ()
+mmix_md_elf_section_change_hook (void)
 {
   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);
+  last_alignment = bfd_section_alignment (now_seg);
   want_unaligned = 0;
 }
 
@@ -3819,8 +3943,7 @@ mmix_md_elf_section_change_hook ()
    section too.   */
 
 static void
-s_loc (ignore)
-     int ignore ATTRIBUTE_UNUSED;
+s_loc (int ignore ATTRIBUTE_UNUSED)
 {
   segT section;
   expressionS exp;
@@ -3828,7 +3951,7 @@ s_loc (ignore)
   symbolS *sym;
   offsetT off;
 
-  /* Must not have a BSPEC in progress. */
+  /* Must not have a BSPEC in progress.  */
   if (doing_bspec)
     {
       as_bad (_("directive LOC from within a BSPEC/ESPEC pair is not supported"));
@@ -3839,20 +3962,39 @@ s_loc (ignore)
 
   if (exp.X_op == O_illegal
       || exp.X_op == O_absent
-      || exp.X_op == O_big
-      || section == undefined_section)
+      || exp.X_op == O_big)
     {
       as_bad (_("invalid LOC expression"));
       return;
     }
 
+  if (section == undefined_section)
+    {
+      /* This is an error or a LOC with an expression involving
+        forward references.  For the expression to be correctly
+        evaluated, we need to force a proper symbol; gas loses track
+        of the segment for "local symbols".  */
+      if (exp.X_op == O_add)
+       {
+         symbol_get_value_expression (exp.X_op_symbol);
+         symbol_get_value_expression (exp.X_add_symbol);
+       }
+      else
+       {
+         gas_assert (exp.X_op == O_symbol);
+         symbol_get_value_expression (exp.X_add_symbol);
+       }
+    }
+
   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.  */
+         /* Lower than Data_Segment or in the reserved area (the
+            segment number is >= 0x80, appearing negative) - assume
+            it's .text.  */
          section = text_section;
 
          /* Save the lowest seen location, so we can pass on this
@@ -3864,8 +4006,8 @@ s_loc (ignore)
             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)
+         if (lowest_text_loc != (bfd_vma) -1
+             && (bfd_vma) exp.X_add_number < lowest_text_loc)
            {
              as_bad (_("LOC expression stepping backwards is not supported"));
              exp.X_op = O_absent;
@@ -3888,7 +4030,8 @@ s_loc (ignore)
        }
       else
        {
-         /* Do the same for the .data section.  */
+         /* Do the same for the .data section, except we don't have
+            to worry about exp.X_add_number carrying a sign.  */
          section = data_section;
 
          if (exp.X_add_number < (offsetT) lowest_data_loc)
@@ -3914,7 +4057,9 @@ s_loc (ignore)
        }
     }
 
-  if (section != now_seg)
+  /* If we can't deduce the section, it must be the current one.
+     Below, we arrange to assert this.  */
+  if (section != now_seg && section != undefined_section)
     {
       obj_elf_section_change_hook ();
       subseg_set (section, 0);
@@ -3925,16 +4070,41 @@ s_loc (ignore)
 
   if (exp.X_op != O_absent)
     {
+      symbolS *esym = NULL;
+
       if (exp.X_op != O_constant && exp.X_op != O_symbol)
        {
          /* Handle complex expressions.  */
-         sym = make_expr_symbol (&exp);
+         esym = sym = make_expr_symbol (&exp);
          off = 0;
        }
       else
        {
          sym = exp.X_add_symbol;
          off = exp.X_add_number;
+
+         if (section == undefined_section)
+           {
+             /* We need an expr_symbol when tracking sections.  In
+                order to make this an expr_symbol with file and line
+                tracked, we have to make the exp non-trivial; not an
+                O_symbol with .X_add_number == 0.  The constant part
+                is unused.  */
+             exp.X_add_number = 1;
+             esym = make_expr_symbol (&exp);
+           }
+       }
+
+      /* Track the LOC's where we couldn't deduce the section: assert
+        that we weren't supposed to change section.  */
+      if (section == undefined_section)
+       {
+         struct loc_assert_s *next = loc_asserts;
+         loc_asserts = XNEW (struct loc_assert_s);
+         loc_asserts->next = next;
+         loc_asserts->old_seg = now_seg;
+         loc_asserts->loc_sym = esym;
+         loc_asserts->frag = frag_now;
        }
 
       p = frag_var (rs_org, 1, 1, (relax_substateT) 0, sym, off, (char *) 0);
@@ -3949,10 +4119,9 @@ s_loc (ignore)
    by comma.  */
 
 static void
-mmix_byte ()
+mmix_byte (void)
 {
   unsigned int c;
-  char *start;
 
   if (now_seg == text_section)
     text_has_contents = 1;
@@ -3966,7 +4135,6 @@ mmix_byte ()
        {
        case '\"':
          ++input_line_pointer;
-         start = input_line_pointer;
          while (is_a_char (c = next_char_of_string ()))
            {
              FRAG_APPEND_1_CHAR (c);
@@ -4039,11 +4207,9 @@ mmix_byte ()
    lenient than mmix_byte but FIXME: they should eventually merge.  */
 
 static void
-mmix_cons (nbytes)
-     int nbytes;
+mmix_cons (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
@@ -4102,7 +4268,7 @@ mmix_cons (nbytes)
 
   SKIP_WHITESPACE ();
 
-  if (is_end_of_line [(unsigned int) *input_line_pointer])
+  if (is_end_of_line[(unsigned int) *input_line_pointer])
     {
       /* Default to zero if the expression was absent.  */
 
@@ -4124,7 +4290,6 @@ mmix_cons (nbytes)
               bytes.  */
          case '\"':
            ++input_line_pointer;
-           start = input_line_pointer;
            while (is_a_char (c = next_char_of_string ()))
              {
                exp.X_op = O_constant;
@@ -4169,11 +4334,8 @@ mmix_cons (nbytes)
    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;
+mmix_md_do_align (int n, char *fill ATTRIBUTE_UNUSED,
+                 int len ATTRIBUTE_UNUSED, int max ATTRIBUTE_UNUSED)
 {
   last_alignment = n;
   want_unaligned = n == 0;