bpf: gas: support relaxation of V4 jump instructions
authorJose E. Marchesi <jose.marchesi@oracle.com>
Thu, 27 Jul 2023 16:17:35 +0000 (18:17 +0200)
committerJose E. Marchesi <jose.marchesi@oracle.com>
Fri, 28 Jul 2023 16:19:44 +0000 (18:19 +0200)
The BPF jump-always instruction (JA), like all other jump instructions
in the ISA, get a signed 16-bit displacement target argument denoted
in number of 64-bit words minus one.  This can sometimes be overflown.

The BPF V4 ISA thus introduced support for a jump-always
instruction (JAL) that gets a signed 32-bit displacement instead.

This patch makes the BPF assembler to perform the following
relaxations when the disp16 field gets overflown, unless the option
-mno-relax is specified:

  JA disp16  -> JAL disp32
  Jxx disp16 -> Jxx +1; JA +1; JAL disp32

Documentation and tests added.
Tested in bpf-unknown-none.

gas/ChangeLog:

2023-07-28  Jose E. Marchesi  <jose.marchesi@oracle.com>

PR gas/30690
* config/tc-bpf.c (struct bpf_insn): Add fields is_relaxable and
relaxed_exp.
(enum options): Add OPTION_NO_RELAX.
(md_longopts): Likewise for -mno-relax.
(do_relax): New global.
(md_parse_option): Handle OPTION_NO_RELAX.
(RELAX_BRANCH_ENCODE): Define.
(RELAX_BRANCH_P): Likewise.
(RELAX_BRANCH_LENGTH): Likewise.
(RELAX_BRANCH_CONST): Likewise.
(RELAX_BRANCH_UNCOND): Likewise.
(relaxed_branch_length): New function.
(md_estimate_size_before_relax): Likewise.
(read_insn_word): Likewise.
(encode_int16): Likewise.
(encode_int32): Likewise.
(write_insn_bytes): Likewise.
(md_convert_frag): Likewise.
(encode_insn): Likewise.
(install_insn_fixups): Likewise.
(add_fixed_insn): Likewise.
(add_relaxed_insn): Likewise.
(md_assemble): Move instruction encoding logic to the above
new functions.
* testsuite/gas/bpf/jump-relax-ja.d: New test.
* testsuite/gas/bpf/jump-relax-ja-be.d: Likewise.
* testsuite/gas/bpf/jump-relax-ja.s: And corresponding source.
* testsuite/gas/bpf/jump-relax-jump.d: New test.
* testsuite/gas/bpf/jump-relax-jump-be.d: Likewise.
* testsuite/gas/bpf/jump-relax-jump.s: And corresponding source.
* testsuite/gas/bpf/bpf.exp: Run new tests.
* doc/c-bpf.texi (BPF Options): Document -mno-relax.

gas/ChangeLog
gas/config/tc-bpf.c
gas/doc/c-bpf.texi
gas/testsuite/gas/bpf/bpf.exp
gas/testsuite/gas/bpf/jump-relax-ja-be.d [new file with mode: 0644]
gas/testsuite/gas/bpf/jump-relax-ja.d [new file with mode: 0644]
gas/testsuite/gas/bpf/jump-relax-ja.s [new file with mode: 0644]
gas/testsuite/gas/bpf/jump-relax-jump.d [new file with mode: 0644]
gas/testsuite/gas/bpf/jump-relax-jump.s [new file with mode: 0644]

index 147c20c460d7338300629fcb3711db8549da0819..1f3735d70afb841afc36ceee601a447d62aa6c64 100644 (file)
@@ -1,3 +1,39 @@
+2023-07-28  Jose E. Marchesi  <jose.marchesi@oracle.com>
+
+       PR gas/30690
+       * config/tc-bpf.c (struct bpf_insn): Add fields is_relaxable and
+       relaxed_exp.
+       (enum options): Add OPTION_NO_RELAX.
+       (md_longopts): Likewise for -mno-relax.
+       (do_relax): New global.
+       (md_parse_option): Handle OPTION_NO_RELAX.
+       (RELAX_BRANCH_ENCODE): Define.
+       (RELAX_BRANCH_P): Likewise.
+       (RELAX_BRANCH_LENGTH): Likewise.
+       (RELAX_BRANCH_CONST): Likewise.
+       (RELAX_BRANCH_UNCOND): Likewise.
+       (relaxed_branch_length): New function.
+       (md_estimate_size_before_relax): Likewise.
+       (read_insn_word): Likewise.
+       (encode_int16): Likewise.
+       (encode_int32): Likewise.
+       (write_insn_bytes): Likewise.
+       (md_convert_frag): Likewise.
+       (encode_insn): Likewise.
+       (install_insn_fixups): Likewise.
+       (add_fixed_insn): Likewise.
+       (add_relaxed_insn): Likewise.
+       (md_assemble): Move instruction encoding logic to the above
+       new functions.
+       * testsuite/gas/bpf/jump-relax-ja.d: New test.
+       * testsuite/gas/bpf/jump-relax-ja-be.d: Likewise.
+       * testsuite/gas/bpf/jump-relax-ja.s: And corresponding source.
+       * testsuite/gas/bpf/jump-relax-jump.d: New test.
+       * testsuite/gas/bpf/jump-relax-jump-be.d: Likewise.
+       * testsuite/gas/bpf/jump-relax-jump.s: And corresponding source.
+       * testsuite/gas/bpf/bpf.exp: Run new tests.
+       * doc/c-bpf.texi (BPF Options): Document -mno-relax.
+
 2023-07-26  Jose E. Marchesi  <jose.marchesi@oracle.com>
 
        * testsuite/gas/bpf/alu.s: Add test for NEGI and NEG32I.
index faa809c05a21eb05c8b4aa03ddf6cbb80c7dddd2..969116bc5adae1bcd705c2e070299db99f63341c 100644 (file)
@@ -51,6 +51,9 @@ struct bpf_insn
   unsigned int has_disp32 : 1;
   unsigned int has_imm32 : 1;
   unsigned int has_imm64 : 1;
+
+  unsigned int is_relaxable : 1;
+  expressionS *relaxed_exp;
 };
 
 const char comment_chars[]        = ";#";
@@ -120,6 +123,7 @@ enum options
   OPTION_XBPF,
   OPTION_DIALECT,
   OPTION_ISA_SPEC,
+  OPTION_NO_RELAX,
 };
 
 struct option md_longopts[] =
@@ -129,6 +133,7 @@ struct option md_longopts[] =
   { "mxbpf", no_argument, NULL, OPTION_XBPF },
   { "mdialect", required_argument, NULL, OPTION_DIALECT},
   { "misa-spec", required_argument, NULL, OPTION_ISA_SPEC},
+  { "mno-relax", no_argument, NULL, OPTION_NO_RELAX},
   { NULL,          no_argument, NULL, 0 },
 };
 
@@ -144,6 +149,11 @@ const char * md_shortopts = "";
 static int set_target_endian = 0;
 extern int target_big_endian;
 
+/* Whether to relax branch instructions.  Default is yes.  Can be
+   changed using the -mno-relax command line option.  */
+
+static int do_relax = 1;
+
 /* The ISA specification can be one of BPF_V1, BPF_V2, BPF_V3, BPF_V4
    or BPF_XPBF.  The ISA spec to use can be configured using
    command-line options.  It defaults to the latest BPF spec.  */
@@ -203,6 +213,9 @@ md_parse_option (int c, const char * arg)
       /* This is an alias for -misa-spec=xbpf.  */
       isa_spec = BPF_XBPF;
       break;
+    case OPTION_NO_RELAX:
+      do_relax = 0;
+      break;
     default:
       return 0;
     }
@@ -337,6 +350,151 @@ tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixP)
 }
 
 \f
+/* Relaxations supported by this assembler.  */
+
+#define RELAX_BRANCH_ENCODE(uncond, constant, length)    \
+  ((relax_substateT)                                     \
+   (0xc0000000                                           \
+    | ((uncond) ? 1 : 0)                                 \
+    | ((constant) ? 2 : 0)                               \
+    | ((length) << 2)))
+
+#define RELAX_BRANCH_P(i) (((i) & 0xf0000000) == 0xc0000000)
+#define RELAX_BRANCH_LENGTH(i) (((i) >> 2) & 0xff)
+#define RELAX_BRANCH_CONST(i) (((i) & 2) != 0)
+#define RELAX_BRANCH_UNCOND(i) (((i) & 1) != 0)
+
+
+/* Compute the length of a branch seuqence, and adjust the stored
+   length accordingly.  If FRAG is NULL, the worst-case length is
+   returned.  */
+
+static unsigned
+relaxed_branch_length (fragS *fragp, asection *sec, int update)
+{
+  int length, uncond;
+
+  if (!fragp)
+    return 8 * 3;
+
+  uncond = RELAX_BRANCH_UNCOND (fragp->fr_subtype);
+  length = RELAX_BRANCH_LENGTH (fragp->fr_subtype);
+
+  if (uncond)
+    /* Length is the same for both JA and JAL.  */
+    length = 8;
+  else
+    {
+      if (RELAX_BRANCH_CONST (fragp->fr_subtype))
+        {
+          int64_t val = fragp->fr_offset;
+
+          if (val < -32768 || val > 32767)
+            length =  8 * 3;
+          else
+            length = 8;
+        }
+      else if (fragp->fr_symbol != NULL
+          && S_IS_DEFINED (fragp->fr_symbol)
+          && !S_IS_WEAK (fragp->fr_symbol)
+          && sec == S_GET_SEGMENT (fragp->fr_symbol))
+        {
+          offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset;
+
+          /* Convert to 64-bit words, minus one.  */
+          val = (val - 8) / 8;
+
+          /* See if it fits in the signed 16-bits field.  */
+          if (val < -32768 || val > 32767)
+            length = 8 * 3;
+          else
+            length = 8;
+        }
+      else
+        /* Use short version, and let the linker relax instead, if
+           appropriate and if supported.  */
+        length = 8;
+    }
+
+  if (update)
+    fragp->fr_subtype = RELAX_BRANCH_ENCODE (uncond,
+                                             RELAX_BRANCH_CONST (fragp->fr_subtype),
+                                             length);
+
+  return length;
+}
+
+/* Estimate the size of a variant frag before relaxing.  */
+
+int
+md_estimate_size_before_relax (fragS *fragp, asection *sec)
+{
+  return (fragp->fr_var = relaxed_branch_length (fragp, sec, true));
+}
+
+/* Read a BPF instruction word from BUF.  */
+
+static uint64_t
+read_insn_word (bfd_byte *buf)
+{
+  return bfd_getb64 (buf);
+}
+
+/* Write the given signed 16-bit value in the given BUFFER using the
+   target endianness.  */
+
+static void
+encode_int16 (int16_t value, char *buffer)
+{
+  uint16_t val = value;
+
+  if (target_big_endian)
+    {
+      buffer[0] = (val >> 8) & 0xff;
+      buffer[1] = val & 0xff;
+    }
+  else
+    {
+      buffer[1] = (val >> 8) & 0xff;
+      buffer[0] = val & 0xff;
+    }
+}
+
+/* Write the given signed 32-bit value in the given BUFFER using the
+   target endianness.  */
+
+static void
+encode_int32 (int32_t value, char *buffer)
+{
+  uint32_t val = value;
+
+  if (target_big_endian)
+    {
+      buffer[0] = (val >> 24) & 0xff;
+      buffer[1] = (val >> 16) & 0xff;
+      buffer[2] = (val >> 8) & 0xff;
+      buffer[3] = val & 0xff;
+    }
+  else
+    {
+      buffer[3] = (val >> 24) & 0xff;
+      buffer[2] = (val >> 16) & 0xff;
+      buffer[1] = (val >> 8) & 0xff;
+      buffer[0] = value & 0xff;
+    }
+}
+
+/* Write a BPF instruction to BUF.  */
+
+static void
+write_insn_bytes (bfd_byte *buf, char *bytes)
+{
+  int i;
+
+  for (i = 0; i < 8; ++i)
+    md_number_to_chars ((char *) buf + i, (valueT) bytes[i], 1);
+}
+
 /* *FRAGP has been relaxed to its final size, and now needs to have
    the bytes inside it modified to conform to the new size.
 
@@ -347,17 +505,229 @@ tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixP)
 void
 md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED,
                 segT sec ATTRIBUTE_UNUSED,
-                fragS *fragP ATTRIBUTE_UNUSED)
+                fragS *fragp ATTRIBUTE_UNUSED)
 {
-  as_fatal (_("convert_frag called"));
-}
+  bfd_byte *buf = (bfd_byte *) fragp->fr_literal + fragp->fr_fix;
+  expressionS exp;
+  fixS *fixp;
+  bpf_insn_word word;
+  int disp_is_known = 0;
+  int64_t disp_to_target = 0;
 
-int
-md_estimate_size_before_relax (fragS *fragP ATTRIBUTE_UNUSED,
-                               segT segment ATTRIBUTE_UNUSED)
-{
-  as_fatal (_("estimate_size_before_relax called"));
-  return 0;
+  uint64_t code;
+
+  gas_assert (RELAX_BRANCH_P (fragp->fr_subtype));
+
+  /* Expression to be used in any resulting relocation in the relaxed
+     instructions.  */
+  exp.X_op = O_symbol;
+  exp.X_add_symbol = fragp->fr_symbol;
+  exp.X_add_number = fragp->fr_offset;
+
+  gas_assert (fragp->fr_var == RELAX_BRANCH_LENGTH (fragp->fr_subtype));
+
+  /* Read an instruction word from the instruction to be relaxed, and
+     get the code.  */
+  word = read_insn_word (buf);
+  code = (word >> 60) & 0xf;
+
+  /* Determine whether the 16-bit displacement to the target is known
+     at this point.  */
+  if (RELAX_BRANCH_CONST (fragp->fr_subtype))
+    {
+      /* XXX this loses the 32-bit value if the constant was
+         overflown! */
+      disp_to_target = fragp->fr_offset;
+      disp_is_known = 1;
+    }
+  else if (fragp->fr_symbol != NULL
+           && S_IS_DEFINED (fragp->fr_symbol)
+           && !S_IS_WEAK (fragp->fr_symbol)
+           && sec == S_GET_SEGMENT (fragp->fr_symbol))
+    {
+      offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset;
+      /* Convert to 64-bit blocks minus one.  */
+      disp_to_target = (val - 8) / 8;
+      disp_is_known = 1;
+    }
+
+  /* Now relax particular jump instructions.  */
+  if (code == BPF_CODE_JA)
+    {
+      /* Unconditional jump.
+         JA d16 -> JAL d32  */
+
+      gas_assert (RELAX_BRANCH_UNCOND (fragp->fr_subtype));
+
+      if (disp_is_known)
+        {
+          if (disp_to_target >= -32768 && disp_to_target <= 32767)
+            {
+              /* 16-bit disp is known and in range.  Install a fixup
+                 for the disp16 if the branch value is not constant.
+                 This will be resolved by the assembler and units
+                 converted.  */
+
+              if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
+                {
+                  /* Install fixup for the JA.  */
+                  reloc_howto_type *reloc_howto
+                    = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+                  if (!reloc_howto)
+                    abort();
+
+                  fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+                                      bfd_get_reloc_size (reloc_howto),
+                                      &exp,
+                                      reloc_howto->pc_relative,
+                                      BFD_RELOC_BPF_DISP16);
+                  fixp->fx_file = fragp->fr_file;
+                  fixp->fx_line = fragp->fr_line;
+                }
+            }
+          else
+            {
+              /* 16-bit disp is known and not in range.  Turn the JA
+                 into a JAL with a 32-bit displacement.  */
+              char bytes[8];
+
+              bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
+              bytes[1] = (word >> 48) & 0xff;
+              bytes[2] = 0; /* disp16 high */
+              bytes[3] = 0; /* disp16 lo */
+              encode_int32 ((int32_t) disp_to_target, bytes + 4);
+
+              write_insn_bytes (buf, bytes);
+            }
+        }
+      else
+        {
+          /* The displacement to the target is not known.  Do not
+             relax.  The linker will maybe do it if it chooses to.  */
+
+          reloc_howto_type *reloc_howto = NULL;
+
+          gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
+
+          /* Install fixup for the JA.  */
+          reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+          if (!reloc_howto)
+            abort ();
+
+          fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+                              bfd_get_reloc_size (reloc_howto),
+                              &exp,
+                              reloc_howto->pc_relative,
+                              BFD_RELOC_BPF_DISP16);
+          fixp->fx_file = fragp->fr_file;
+          fixp->fx_line = fragp->fr_line;
+        }
+
+      buf += 8;
+    }
+  else
+    {
+      /* Conditional jump.
+         JXX d16 -> JXX +1; JA +1; JAL d32 */
+
+      gas_assert (!RELAX_BRANCH_UNCOND (fragp->fr_subtype));
+
+      if (disp_is_known)
+        {
+          if (disp_to_target >= -32768 && disp_to_target <= 32767)
+            {
+              /* 16-bit disp is known and in range.  Install a fixup
+                 for the disp16 if the branch value is not constant.
+                 This will be resolved by the assembler and units
+                 converted.  */
+
+              if (!RELAX_BRANCH_CONST (fragp->fr_subtype))
+                {
+                  /* Install fixup for the branch.  */
+                  reloc_howto_type *reloc_howto
+                    = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+                  if (!reloc_howto)
+                    abort();
+
+                  fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+                                      bfd_get_reloc_size (reloc_howto),
+                                      &exp,
+                                      reloc_howto->pc_relative,
+                                      BFD_RELOC_BPF_DISP16);
+                  fixp->fx_file = fragp->fr_file;
+                  fixp->fx_line = fragp->fr_line;
+                }
+
+              buf += 8;
+            }
+          else
+            {
+              /* 16-bit disp is known and not in range.  Turn the JXX
+                 into a sequence JXX +1; JA +1; JAL d32.  */
+
+              char bytes[8];
+
+              /* First, set the 16-bit offset in the current
+                 instruction to 1.  */
+
+              if (target_big_endian)
+                bfd_putb16 (1, buf + 2);
+              else
+                bfd_putl16 (1, buf + 2);
+              buf += 8;
+
+              /* Then, write the JA + 1  */
+
+              bytes[0] = 0x05; /* JA */
+              bytes[1] = 0x0;
+              encode_int16 (1, bytes + 2);
+              bytes[4] = 0x0;
+              bytes[5] = 0x0;
+              bytes[6] = 0x0;
+              bytes[7] = 0x0;
+              write_insn_bytes (buf, bytes);
+              buf += 8;
+
+              /* Finally, write the JAL to the target. */
+
+              bytes[0] = ((BPF_CLASS_JMP32|BPF_CODE_JA|BPF_SRC_K) >> 56) & 0xff;
+              bytes[1] = 0;
+              bytes[2] = 0;
+              bytes[3] = 0;
+              encode_int32 ((int32_t) disp_to_target, bytes + 4);
+              write_insn_bytes (buf, bytes);
+              buf += 8;
+            }
+        }
+      else
+        {
+          /* The displacement to the target is not known.  Do not
+             relax.  The linker will maybe do it if it chooses to.  */
+
+          reloc_howto_type *reloc_howto = NULL;
+
+          gas_assert (!RELAX_BRANCH_CONST (fragp->fr_subtype));
+
+          /* Install fixup for the conditional jump.  */
+          reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+          if (!reloc_howto)
+            abort ();
+
+          fixp = fix_new_exp (fragp, buf - (bfd_byte *) fragp->fr_literal,
+                              bfd_get_reloc_size (reloc_howto),
+                              &exp,
+                              reloc_howto->pc_relative,
+                              BFD_RELOC_BPF_DISP16);
+          fixp->fx_file = fragp->fr_file;
+          fixp->fx_line = fragp->fr_line;
+          buf += 8;
+        }
+    }
+
+  gas_assert (buf == (bfd_byte *)fragp->fr_literal
+              + fragp->fr_fix + fragp->fr_var);
+
+  fragp->fr_fix += fragp->fr_var;
 }
 
 \f
@@ -460,6 +830,327 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
   fixP->fx_addnumber = *valP;
 }
 
+\f
+/* Instruction writing routines.  */
+
+/* Encode a BPF instruction in the given buffer BYTES.  Non-constant
+   immediates are encoded as zeroes.  */
+
+static void
+encode_insn (struct bpf_insn *insn, char *bytes)
+{
+  uint8_t src, dst;
+
+  /* Zero all the bytes.  */
+  memset (bytes, 0, 16);
+
+  /* First encode the opcodes.  Note that we have to handle the
+     endianness groups of the BPF instructions: 8 | 4 | 4 | 16 |
+     32. */
+  if (target_big_endian)
+    {
+      /* code */
+      bytes[0] = (insn->opcode >> 56) & 0xff;
+      /* regs */
+      bytes[1] = (insn->opcode >> 48) & 0xff;
+      /* offset16 */
+      bytes[2] = (insn->opcode >> 40) & 0xff;
+      bytes[3] = (insn->opcode >> 32) & 0xff;
+      /* imm32 */
+      bytes[4] = (insn->opcode >> 24) & 0xff;
+      bytes[5] = (insn->opcode >> 16) & 0xff;
+      bytes[6] = (insn->opcode >> 8) & 0xff;
+      bytes[7] = insn->opcode & 0xff;
+    }
+  else
+    {
+      /* code */
+      bytes[0] = (insn->opcode >> 56) & 0xff;
+      /* regs */
+      bytes[1] = (((((insn->opcode >> 48) & 0xff) & 0xf) << 4)
+                  | (((insn->opcode >> 48) & 0xff) & 0xf));
+      /* offset16 */
+      bytes[3] = (insn->opcode >> 40) & 0xff;
+      bytes[2] = (insn->opcode >> 32) & 0xff;
+      /* imm32 */
+      bytes[7] = (insn->opcode >> 24) & 0xff;
+      bytes[6] = (insn->opcode >> 16) & 0xff;
+      bytes[5] = (insn->opcode >> 8) & 0xff;
+      bytes[4] = insn->opcode & 0xff;
+    }
+
+  /* Now the registers.  */
+  src = insn->has_src ? insn->src : 0;
+  dst = insn->has_dst ? insn->dst : 0;
+
+  if (target_big_endian)
+    bytes[1] = ((dst & 0xf) << 4) | (src & 0xf);
+  else
+    bytes[1] = ((src & 0xf) << 4) | (dst & 0xf);
+
+  /* Now the immediates that are known to be constant.  */
+
+  if (insn->has_imm32 && insn->imm32.X_op == O_constant)
+    encode_int32 (insn->imm32.X_add_number, bytes + 4);
+
+  if (insn->has_disp32 && insn->disp32.X_op == O_constant)
+    encode_int32 (insn->disp32.X_add_number, bytes + 4);
+
+  if (insn->has_offset16 && insn->offset16.X_op == O_constant)
+    encode_int16 (insn->offset16.X_add_number, bytes + 2);
+
+  if (insn->has_disp16 && insn->disp16.X_op == O_constant)
+    encode_int16 (insn->disp16.X_add_number, bytes + 2);
+
+  if (insn->has_imm64 && insn->imm64.X_op == O_constant)
+    {
+      uint64_t imm64 = insn->imm64.X_add_number;
+
+      if (target_big_endian)
+        {
+          bytes[12] = (imm64 >> 56) & 0xff;
+          bytes[13] = (imm64 >> 48) & 0xff;
+          bytes[14] = (imm64 >> 40) & 0xff;
+          bytes[15] = (imm64 >> 32) & 0xff;
+          bytes[4] = (imm64 >> 24) & 0xff;
+          bytes[5] = (imm64 >> 16) & 0xff;
+          bytes[6] = (imm64 >> 8) & 0xff;
+          bytes[7] = imm64 & 0xff;
+        }
+      else
+        {
+          bytes[15] = (imm64 >> 56) & 0xff;
+          bytes[14] = (imm64 >> 48) & 0xff;
+          bytes[13] = (imm64 >> 40) & 0xff;
+          bytes[12] = (imm64 >> 32) & 0xff;
+          bytes[7] = (imm64 >> 24) & 0xff;
+          bytes[6] = (imm64 >> 16) & 0xff;
+          bytes[5] = (imm64 >> 8) & 0xff;
+          bytes[4] = imm64 & 0xff;
+        }
+    }
+}
+
+/* Install the fixups in INSN in their proper location in the
+   specified FRAG at the location pointed by WHERE.  */
+
+static void
+install_insn_fixups (struct bpf_insn *insn, fragS *frag, long where)
+{
+  if (insn->has_imm64)
+    {
+      switch (insn->imm64.X_op)
+        {
+        case O_symbol:
+        case O_subtract:
+        case O_add:
+          {
+            reloc_howto_type *reloc_howto;
+            int size;
+
+            reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_64);
+            if (!reloc_howto)
+              abort ();
+
+            size = bfd_get_reloc_size (reloc_howto);
+
+            fix_new_exp (frag, where,
+                         size, &insn->imm64, reloc_howto->pc_relative,
+                         BFD_RELOC_BPF_64);
+            break;
+          }
+        case O_constant:
+          /* Already handled in encode_insn.  */
+          break;
+        default:
+          abort ();
+        }
+    }
+
+  if (insn->has_imm32)
+    {
+      switch (insn->imm32.X_op)
+        {
+        case O_symbol:
+        case O_subtract:
+        case O_add:
+        case O_uminus:
+          {
+            reloc_howto_type *reloc_howto;
+            int size;
+
+            reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
+            if (!reloc_howto)
+              abort ();
+
+            size = bfd_get_reloc_size (reloc_howto);
+
+            fix_new_exp (frag, where + 4,
+                         size, &insn->imm32, reloc_howto->pc_relative,
+                         BFD_RELOC_32);
+            break;
+          }
+        case O_constant:
+          /* Already handled in encode_insn.  */
+          break;
+        default:
+          abort ();
+        }
+    }
+
+  if (insn->has_disp32)
+    {
+      switch (insn->disp32.X_op)
+        {
+        case O_symbol:
+        case O_subtract:
+        case O_add:
+          {
+            reloc_howto_type *reloc_howto;
+            int size;
+            unsigned int bfd_reloc
+              = (insn->id == BPF_INSN_CALL
+                 ? BFD_RELOC_BPF_DISPCALL32
+                 : BFD_RELOC_BPF_DISP32);
+
+            reloc_howto = bfd_reloc_type_lookup (stdoutput, bfd_reloc);
+            if (!reloc_howto)
+              abort ();
+
+            size = bfd_get_reloc_size (reloc_howto);
+
+            fix_new_exp (frag, where,
+                         size, &insn->disp32, reloc_howto->pc_relative,
+                         bfd_reloc);
+            break;
+          }
+        case O_constant:
+          /* Already handled in encode_insn.  */
+          break;
+        default:
+          abort ();
+        }
+    }
+
+  if (insn->has_offset16)
+    {
+      switch (insn->offset16.X_op)
+        {
+        case O_symbol:
+        case O_subtract:
+        case O_add:
+          {
+            reloc_howto_type *reloc_howto;
+            int size;
+
+            /* XXX we really need a new pc-rel offset in bytes
+               relocation for this.  */
+            reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+            if (!reloc_howto)
+              abort ();
+
+            size = bfd_get_reloc_size (reloc_howto);
+
+            fix_new_exp (frag, where,
+                         size, &insn->offset16, reloc_howto->pc_relative,
+                         BFD_RELOC_BPF_DISP16);
+            break;
+          }
+        case O_constant:
+          /* Already handled in encode_insn.  */
+          break;
+        default:
+          abort ();
+        }
+    }
+
+  if (insn->has_disp16)
+    {
+      switch (insn->disp16.X_op)
+        {
+        case O_symbol:
+        case O_subtract:
+        case O_add:
+          {
+            reloc_howto_type *reloc_howto;
+            int size;
+
+            reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
+            if (!reloc_howto)
+              abort ();
+
+            size = bfd_get_reloc_size (reloc_howto);
+
+            fix_new_exp (frag, where,
+                         size, &insn->disp16, reloc_howto->pc_relative,
+                         BFD_RELOC_BPF_DISP16);
+            break;
+          }
+        case O_constant:
+          /* Already handled in encode_insn.  */
+          break;
+        default:
+          abort ();
+        }
+    }
+
+}
+
+/* Add a new insn to the list of instructions.  */
+
+static void
+add_fixed_insn (struct bpf_insn *insn)
+{
+  char *this_frag = frag_more (insn->size);
+  char bytes[16];
+  int i;
+
+  /* First encode the known parts of the instruction, including
+     opcodes and constant immediates, and write them to the frag.  */
+  encode_insn (insn, bytes);
+  for (i = 0; i < insn->size; ++i)
+    md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
+
+  /* Now install the instruction fixups.  */
+  install_insn_fixups (insn, frag_now,
+                       this_frag - frag_now->fr_literal);
+}
+
+/* Add a new relaxable to the list of instructions.  */
+
+static void
+add_relaxed_insn (struct bpf_insn *insn, expressionS *exp)
+{
+  char bytes[16];
+  int i;
+  char *this_frag;
+  unsigned worst_case = relaxed_branch_length (NULL, NULL, 0);
+  unsigned best_case = insn->size;
+
+  /* We only support relaxing branches, for the moment.  */
+  relax_substateT subtype
+    = RELAX_BRANCH_ENCODE (insn->id == BPF_INSN_JAR,
+                           exp->X_op == O_constant,
+                           worst_case);
+
+  frag_grow (worst_case);
+  this_frag = frag_more (0);
+
+  /* First encode the known parts of the instruction, including
+     opcodes and constant immediates, and write them to the frag.  */
+  encode_insn (insn, bytes);
+  for (i = 0; i < insn->size; ++i)
+    md_number_to_chars (this_frag + i, (valueT) bytes[i], 1);
+
+  /* Note that instruction fixups will be applied once the frag is
+     relaxed, in md_convert_frag.  */
+  frag_var (rs_machine_dependent,
+            worst_case, best_case,
+            subtype, exp->X_add_symbol, exp->X_add_number /* offset */,
+            NULL);
+}
+
+\f
 /* Parse an operand expression.  Returns the first character that is
    not part of the expression, or NULL in case of parse error.
 
@@ -781,6 +1472,7 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
                       break;
                     }
                   insn.has_disp16 = 1;
+                  insn.is_relaxable = 1;
                   p += 4;
                 }
               else if (strncmp (p, "%d32", 4) == 0)
@@ -865,307 +1557,19 @@ md_assemble (char *str ATTRIBUTE_UNUSED)
 #undef PARSE_ERROR
 
   /* Generate the frags and fixups for the parsed instruction.  */
-  {
-    char *this_frag = frag_more (insn.size);
-    char bytes[16];
-    uint8_t src, dst;
-    int i;
-
-    /* Zero all the bytes.  */
-    memset (bytes, 0, 16);
-
-    /* First encode the opcodes.  Note that we have to handle the
-       endianness groups of the BPF instructions: 8 | 4 | 4 | 16 |
-       32. */
-    if (target_big_endian)
-      {
-        /* code */
-        bytes[0] = (insn.opcode >> 56) & 0xff;
-        /* regs */
-        bytes[1] = (insn.opcode >> 48) & 0xff;
-        /* offset16 */
-        bytes[2] = (insn.opcode >> 40) & 0xff;
-        bytes[3] = (insn.opcode >> 32) & 0xff;
-        /* imm32 */
-        bytes[4] = (insn.opcode >> 24) & 0xff;
-        bytes[5] = (insn.opcode >> 16) & 0xff;
-        bytes[6] = (insn.opcode >> 8) & 0xff;
-        bytes[7] = insn.opcode & 0xff;
-      }
-    else
-      {
-        /* code */
-        bytes[0] = (insn.opcode >> 56) & 0xff;
-        /* regs */
-        bytes[1] = (((((insn.opcode >> 48) & 0xff) & 0xf) << 4)
-                    | (((insn.opcode >> 48) & 0xff) & 0xf));
-        /* offset16 */
-        bytes[3] = (insn.opcode >> 40) & 0xff;
-        bytes[2] = (insn.opcode >> 32) & 0xff;
-        /* imm32 */
-        bytes[7] = (insn.opcode >> 24) & 0xff;
-        bytes[6] = (insn.opcode >> 16) & 0xff;
-        bytes[5] = (insn.opcode >> 8) & 0xff;
-        bytes[4] = insn.opcode & 0xff;
-      }
-
-    /* Now the registers.  */
-    src = insn.has_src ? insn.src : 0;
-    dst = insn.has_dst ? insn.dst : 0;
-
-    if (target_big_endian)
-      bytes[1] = ((dst & 0xf) << 4) | (src & 0xf);
-    else
-      bytes[1] = ((src & 0xf) << 4) | (dst & 0xf);
-
-    /* Now the immediates.  */
-    if (insn.has_imm64)
-      {
-        switch (insn.imm64.X_op)
-          {
-          case O_constant:
-            {
-              uint64_t imm64 = insn.imm64.X_add_number;
-
-              if (target_big_endian)
-                {
-                  bytes[12] = (imm64 >> 56) & 0xff;
-                  bytes[13] = (imm64 >> 48) & 0xff;
-                  bytes[14] = (imm64 >> 40) & 0xff;
-                  bytes[15] = (imm64 >> 32) & 0xff;
-                  bytes[4] = (imm64 >> 24) & 0xff;
-                  bytes[5] = (imm64 >> 16) & 0xff;
-                  bytes[6] = (imm64 >> 8) & 0xff;
-                  bytes[7] = imm64 & 0xff;
-                }
-              else
-                {
-                  bytes[15] = (imm64 >> 56) & 0xff;
-                  bytes[14] = (imm64 >> 48) & 0xff;
-                  bytes[13] = (imm64 >> 40) & 0xff;
-                  bytes[12] = (imm64 >> 32) & 0xff;
-                  bytes[7] = (imm64 >> 24) & 0xff;
-                  bytes[6] = (imm64 >> 16) & 0xff;
-                  bytes[5] = (imm64 >> 8) & 0xff;
-                  bytes[4] = imm64 & 0xff;
-                }
-              break;
-            }
-          case O_symbol:
-          case O_subtract:
-          case O_add:
-            {
-              reloc_howto_type *reloc_howto;
-              int size;
-
-              reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_64);
-              if (!reloc_howto)
-                abort ();
-
-              size = bfd_get_reloc_size (reloc_howto);
-
-              fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
-                           size, &insn.imm64, reloc_howto->pc_relative,
-                           BFD_RELOC_BPF_64);
-              break;
-            }
-          default:
-            abort ();
-          }
-      }
-
-    if (insn.has_imm32)
-      {
-        switch (insn.imm32.X_op)
-          {
-          case O_constant:
-            {
-              uint32_t imm32 = insn.imm32.X_add_number;
-
-              if (target_big_endian)
-                {
-                  bytes[4] = (imm32 >> 24) & 0xff;
-                  bytes[5] = (imm32 >> 16) & 0xff;
-                  bytes[6] = (imm32 >> 8) & 0xff;
-                  bytes[7] = imm32 & 0xff;
-                }
-              else
-                {
-                  bytes[7] = (imm32 >> 24) & 0xff;
-                  bytes[6] = (imm32 >> 16) & 0xff;
-                  bytes[5] = (imm32 >> 8) & 0xff;
-                  bytes[4] = imm32 & 0xff;
-                }
-              break;
-            }
-          case O_symbol:
-          case O_subtract:
-          case O_add:
-          case O_uminus:
-            {
-              reloc_howto_type *reloc_howto;
-              int size;
-
-              reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
-              if (!reloc_howto)
-                abort ();
-
-              size = bfd_get_reloc_size (reloc_howto);
-
-              fix_new_exp (frag_now, this_frag - frag_now->fr_literal + 4,
-                           size, &insn.imm32, reloc_howto->pc_relative,
-                           BFD_RELOC_32);
-              break;
-            }
-          default:
-            abort ();
-          }
-      }
-
-    if (insn.has_disp32)
-      {
-        switch (insn.disp32.X_op)
-          {
-          case O_constant:
-            {
-              uint32_t disp32 = insn.disp32.X_add_number;
-
-              if (target_big_endian)
-                {
-                  bytes[4] = (disp32 >> 24) & 0xff;
-                  bytes[5] = (disp32 >> 16) & 0xff;
-                  bytes[6] = (disp32 >> 8) & 0xff;
-                  bytes[7] = disp32 & 0xff;
-                }
-              else
-                {
-                  bytes[7] = (disp32 >> 24) & 0xff;
-                  bytes[6] = (disp32 >> 16) & 0xff;
-                  bytes[5] = (disp32 >> 8) & 0xff;
-                  bytes[4] = disp32 & 0xff;
-                }
-              break;
-            }
-          case O_symbol:
-          case O_subtract:
-          case O_add:
-            {
-              reloc_howto_type *reloc_howto;
-              int size;
-              unsigned int bfd_reloc
-                = (insn.id == BPF_INSN_CALL
-                   ? BFD_RELOC_BPF_DISPCALL32
-                   : BFD_RELOC_BPF_DISP32);
-
-              reloc_howto = bfd_reloc_type_lookup (stdoutput, bfd_reloc);
-              if (!reloc_howto)
-                abort ();
-
-              size = bfd_get_reloc_size (reloc_howto);
-
-              fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
-                           size, &insn.disp32, reloc_howto->pc_relative,
-                           bfd_reloc);
-              break;
-            }
-          default:
-            abort ();
-          }
-      }
-
-    if (insn.has_offset16)
-      {
-        switch (insn.offset16.X_op)
-          {
-          case O_constant:
-            {
-              uint32_t offset16 = insn.offset16.X_add_number;
-
-              if (target_big_endian)
-                {
-                  bytes[2] = (offset16 >> 8) & 0xff;
-                  bytes[3] = offset16 & 0xff;
-                }
-              else
-                {
-                  bytes[3] = (offset16 >> 8) & 0xff;
-                  bytes[2] = offset16 & 0xff;
-                }
-              break;
-            }
-          case O_symbol:
-          case O_subtract:
-          case O_add:
-            {
-              reloc_howto_type *reloc_howto;
-              int size;
-
-              reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
-              if (!reloc_howto)
-                abort ();
-
-              size = bfd_get_reloc_size (reloc_howto);
-
-              fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
-                           size, &insn.offset16, reloc_howto->pc_relative,
-                           BFD_RELOC_BPF_DISP16);
-              break;
-            }
-          default:
-            abort ();
-          }
-      }
-
-    if (insn.has_disp16)
-      {
-        switch (insn.disp16.X_op)
-          {
-          case O_constant:
-            {
-              uint32_t disp16 = insn.disp16.X_add_number;
-
-              if (target_big_endian)
-                {
-                  bytes[2] = (disp16 >> 8) & 0xff;
-                  bytes[3] = disp16 & 0xff;
-                }
-              else
-                {
-                  bytes[3] = (disp16 >> 8) & 0xff;
-                  bytes[2] = disp16 & 0xff;
-                }
-              break;
-            }
-          case O_symbol:
-          case O_subtract:
-          case O_add:
-            {
-              reloc_howto_type *reloc_howto;
-              int size;
-
-              reloc_howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_BPF_DISP16);
-              if (!reloc_howto)
-                abort ();
+  if (do_relax && insn.is_relaxable)
+    {
+      expressionS *relaxable_exp = NULL;
 
-              size = bfd_get_reloc_size (reloc_howto);
+      if (insn.has_disp16)
+        relaxable_exp = &insn.disp16;
+      else
+        abort ();
 
-              fix_new_exp (frag_now, this_frag - frag_now->fr_literal,
-                           size, &insn.disp16, reloc_howto->pc_relative,
-                           BFD_RELOC_BPF_DISP16);
-              break;
-            }
-          default:
-            abort ();
-          }
-      }
-
-    /* Emit bytes.  */
-    for (i = 0; i < insn.size; ++i)
-      {
-        md_number_to_chars (this_frag, (valueT) bytes[i], 1);
-        this_frag += 1;
-      }
-  }
+      add_relaxed_insn (&insn, relaxable_exp);
+    }
+  else
+    add_fixed_insn (&insn);
 
   /* Emit DWARF2 debugging information.  */
   dwarf2_emit_insn (insn.size);
index 868a358cf541129befb8181ef3178d1df0cbc2d4..6b43c77d5a08d6af110e09a2a1b95a393ba31bf5 100644 (file)
@@ -53,6 +53,10 @@ when assembling.  The BPF ISA versions supported are @option{v1} @option{v2}, @o
 The value @option{xbpf} can be specified to recognize extra
 instructions that are used by GCC for testing purposes.  But beware
 this is not valid BPF.
+
+@cindex @option{-mno-relax} command-line options, BPF
+@item -mno-relax
+This option tells the assembler to not relax instructions.
 @end table
 
 Note that if no endianness option is specified in the command line,
index 1d683d55215f10668884a322d4e37931a69a3a72..6e6a0003e17bfcf7a8e14841178b00da2b4ead5b 100644 (file)
@@ -40,6 +40,9 @@ if {[istarget bpf*-*-*]} {
     run_dump_test indcall-1
     run_dump_test indcall-1-pseudoc
 
+    run_dump_test jump-relax-ja
+    run_dump_test jump-relax-jump
+
     # Big-endian BPF tests
     run_dump_test call-be
     run_dump_test exit-be
@@ -59,4 +62,7 @@ if {[istarget bpf*-*-*]} {
     run_dump_test atomic-v1-be
     run_dump_test atomic-be
     run_dump_test atomic-be-pseudoc
+
+    run_dump_test jump-relax-ja-be
+    run_dump_test jump-relax-jump-be
 }
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja-be.d b/gas/testsuite/gas/bpf/jump-relax-ja-be.d
new file mode 100644 (file)
index 0000000..08b85a9
--- /dev/null
@@ -0,0 +1,19 @@
+#as: -EB -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-ja.s
+#name: Relaxation of unconditional branch (JA) instructions, big-endian
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+   0:  05 00 80 00 00 00 00 00         ja -32768
+   8:  05 00 7f ff 00 00 00 00         ja 32767
+  10:  05 00 ff fd 00 00 00 00         ja -3
+  18:  05 00 00 00 00 00 00 00         ja 0
+                       18: R_BPF_GNU_64_16     undefined
+  20:  06 00 00 00 ff ff 7f ff         jal -32769
+  28:  06 00 00 00 00 00 80 00         jal 32768
+  30:  06 00 00 00 00 00 80 01         jal 32769
+  38:  06 00 00 00 00 00 80 01         jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja.d b/gas/testsuite/gas/bpf/jump-relax-ja.d
new file mode 100644 (file)
index 0000000..6b50973
--- /dev/null
@@ -0,0 +1,19 @@
+#as: -EL -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-ja.s
+#name: Relaxation of unconditional branch (JA) instructions
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+   0:  05 00 00 80 00 00 00 00         ja -32768
+   8:  05 00 ff 7f 00 00 00 00         ja 32767
+  10:  05 00 fd ff 00 00 00 00         ja -3
+  18:  05 00 00 00 00 00 00 00         ja 0
+                       18: R_BPF_GNU_64_16     undefined
+  20:  06 00 00 00 ff 7f ff ff         jal -32769
+  28:  06 00 00 00 00 80 00 00         jal 32768
+  30:  06 00 00 00 01 80 00 00         jal 32769
+  38:  06 00 00 00 01 80 00 00         jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-ja.s b/gas/testsuite/gas/bpf/jump-relax-ja.s
new file mode 100644 (file)
index 0000000..8be3d7a
--- /dev/null
@@ -0,0 +1,20 @@
+        ;; The following two instructions have constant targets that
+        ;; fix in the JA 16-bit signed displacement operand.  These
+        ;; are not relaxed.
+1:      ja -32768
+        ja 32767
+        ;; The following instruction refers to a defined symbol that
+        ;; is on reach, so it should not be relaxed.
+        ja 1b
+        ;; The following instruction has an undefined symbol as a
+        ;; target.  It is not to be relaxed.
+        ja undefined + 10
+        ;; The following instructions are relaxed to JAL instructions
+        ;; so they can fit their displacements.
+        ja -32769
+        ja 32768
+        ;; The following instructions refer to a defined symbol that
+        ;; is not on reach.  They shall be relaxed to a JAL.
+        ja tail
+        tail = .text + 262160
+        ja tail
diff --git a/gas/testsuite/gas/bpf/jump-relax-jump.d b/gas/testsuite/gas/bpf/jump-relax-jump.d
new file mode 100644 (file)
index 0000000..cd46ea7
--- /dev/null
@@ -0,0 +1,25 @@
+#as: -EL -mdialect=normal
+#objdump: -dr -M dec
+#source: jump-relax-jump.s
+#name: Relaxation of conditional branch instructions
+
+.*: +file format .*bpf.*
+
+Disassembly of section .text:
+
+0+ <.*>:
+   0:  1d 21 00 80 00 00 00 00         jeq %r1,%r2,-32768
+   8:  ad 21 ff 7f 00 00 00 00         jlt %r1,%r2,32767
+  10:  bd 21 fd ff 00 00 00 00         jle %r1,%r2,-3
+  18:  3d 21 01 00 00 00 00 00         jge %r1,%r2,1
+  20:  05 00 01 00 00 00 00 00         ja 1
+  28:  06 00 00 00 ff 7f ff ff         jal -32769
+  30:  2d 21 01 00 00 00 00 00         jgt %r1,%r2,1
+  38:  05 00 01 00 00 00 00 00         ja 1
+  40:  06 00 00 00 00 80 00 00         jal 32768
+  48:  1d 21 01 00 00 00 00 00         jeq %r1,%r2,1
+  50:  05 00 01 00 00 00 00 00         ja 1
+  58:  06 00 00 00 01 80 00 00         jal 32769
+  60:  2d 21 01 00 00 00 00 00         jgt %r1,%r2,1
+  68:  05 00 01 00 00 00 00 00         ja 1
+  70:  06 00 00 00 01 80 00 00         jal 32769
diff --git a/gas/testsuite/gas/bpf/jump-relax-jump.s b/gas/testsuite/gas/bpf/jump-relax-jump.s
new file mode 100644 (file)
index 0000000..dabbab8
--- /dev/null
@@ -0,0 +1,17 @@
+        ;; The following two instructions have constant targets that
+        ;; fix in the jump 16-bit signed displacement operand.
+1:      jeq %r1, %r2, -32768
+        jlt %r1, %r2, 32767
+        ;; The following instruction refers to a defined symbol that
+        ;; is on reach, so it should not be relaxed.
+        jle %r1, %r2, 1b
+        ;; The following instructions are relaxed to sequences
+        ;; involving unconditional jumps, so they can fi their
+        ;; displacements.
+        jge %r1, %r2, -32769
+        jgt %r1, %r2, 32768
+        ;; The following instructions refer to a defined symbol that
+        ;; is not on reach.  They shall be relaxed.
+        jeq %r1, %r2, tail
+        tail = .text + 262160
+        jgt %r1, %r2, tail