Add opcode relaxation for rl78-elf
authorDJ Delorie <dj@redhat.com>
Thu, 20 Mar 2014 21:50:49 +0000 (17:50 -0400)
committerDJ Delorie <dj@redhat.com>
Thu, 20 Mar 2014 21:56:01 +0000 (17:56 -0400)
This patch adds initial in-gas opcode relaxation for the rl78
backend.  Specifically, it checks for conditional branches that
are too far and replaces them with inverted branches around longer
fixed branches.

gas/ChangeLog
gas/config/rl78-defs.h
gas/config/rl78-parse.y
gas/config/tc-rl78.c
gas/config/tc-rl78.h

index 231c8c5e892ea651d48ae5ca1cbb92b969816bbc..52fb7242a29d8705a78db52476c8a03c049d26d5 100644 (file)
@@ -1,3 +1,19 @@
+2014-03-20  DJ Delorie  <dj@redhat.com>
+
+       * config/rl78-defs.h (RL78_RELAX_NONE, RL78_RELAX_BRANCH): Add.
+       * config/rl78-parse.y (BC, BNC, BZ, BNZ, BH, BHZ, bt_bf): Call
+       rl78_relax().
+       * config/tc-rl78.h (md_relax_frag): Define.
+       (rl78_relax_frag): Declare.
+       * config/tc-rl78.c (rl78_relax): Add.
+       (md_assemble): Set up the variable frags also when relaxing.
+       (op_type_T): New.
+       (rl78_opcode_type): New.
+       (rl78_frag_fix_value): New.
+       (md_estimate_size_before_relax): New-ish.
+       (rl78_relax_frag): New.
+       (md_convert_frag): New-ish.
+
 2014-03-20  Richard Sandiford  <rdsandiford@googlemail.com>
 
        * config/tc-mips.h (DIFF_EXPR_OK, CFI_DIFF_EXPR_OK): Define.
index e4167062f35ef8dfd7892724b25617fca18e22fb..0af8874e9da7af8d4200e7ee9709fb84d412e363 100644 (file)
@@ -25,6 +25,9 @@
 #define RL78REL_DATA           0
 #define RL78REL_PCREL          1
 
+#define RL78_RELAX_NONE                0
+#define RL78_RELAX_BRANCH      1
+
 extern int    rl78_error (const char *);
 extern void   rl78_lex_init (char *, char *);
 extern void   rl78_prefix (int);
index 1ef3c8dfd829bfb9dac9b2bee0b4779af836c301..e358a27bbac0d3e915045c49b5fe726e464f647c 100644 (file)
@@ -290,22 +290,22 @@ statement :
 /* ---------------------------------------------------------------------- */
 
        | BC '$' EXPR
-         { B1 (0xdc); PC1 ($3); }
+         { B1 (0xdc); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
        | BNC '$' EXPR
-         { B1 (0xde); PC1 ($3); }
+         { B1 (0xde); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
        | BZ '$' EXPR
-         { B1 (0xdd); PC1 ($3); }
+         { B1 (0xdd); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
        | BNZ '$' EXPR
-         { B1 (0xdf); PC1 ($3); }
+         { B1 (0xdf); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
        | BH '$' EXPR
-         { B2 (0x61, 0xc3); PC1 ($3); }
+         { B2 (0x61, 0xc3); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
        | BNH '$' EXPR
-         { B2 (0x61, 0xd3); PC1 ($3); }
+         { B2 (0x61, 0xd3); PC1 ($3); rl78_relax (RL78_RELAX_BRANCH, 0); }
 
 /* ---------------------------------------------------------------------- */
 
@@ -1153,12 +1153,12 @@ addsubw : ADDW  { $$ = 0x00; }
        ;
 
 andor1 : AND1 { $$ = 0x05; rl78_bit_insn = 1; }
-       | OR1  { $$ = 0x06; rl78_bit_insn = 1;}
+       | OR1  { $$ = 0x06; rl78_bit_insn = 1; }
        | XOR1 { $$ = 0x07; rl78_bit_insn = 1; }
        ;
 
-bt_bf  : BT { $$ = 0x02;    rl78_bit_insn = 1;}
-       | BF { $$ = 0x04;    rl78_bit_insn = 1; }
+bt_bf  : BT { $$ = 0x02;    rl78_bit_insn = 1; rl78_relax (RL78_RELAX_BRANCH, 0); }
+       | BF { $$ = 0x04;    rl78_bit_insn = 1; rl78_relax (RL78_RELAX_BRANCH, 0); }
        | BTCLR { $$ = 0x00; rl78_bit_insn = 1; }
        ;
 
index 04c9b2c845263efdccb7501fba1fb252f7a64bb7..962a0b02a8f93265245832ef54efd6a8a6f5d3f1 100644 (file)
@@ -85,6 +85,15 @@ typedef struct rl78_bytesT
 
 static rl78_bytesT rl78_bytes;
 
+void
+rl78_relax (int type, int pos)
+{
+  rl78_bytes.relax[rl78_bytes.n_relax].type = type;
+  rl78_bytes.relax[rl78_bytes.n_relax].field_pos = pos;
+  rl78_bytes.relax[rl78_bytes.n_relax].val_ofs = rl78_bytes.n_base + rl78_bytes.n_ops;
+  rl78_bytes.n_relax ++;
+}
+
 void
 rl78_linkrelax_addr16 (void)
 {
@@ -486,12 +495,13 @@ md_assemble (char * str)
   rl78_parse ();
 
   /* This simplifies the relaxation code.  */
-  if (rl78_bytes.link_relax)
+  if (rl78_bytes.n_relax || rl78_bytes.link_relax)
     {
       int olen = rl78_bytes.n_prefix + rl78_bytes.n_base + rl78_bytes.n_ops;
       /* We do it this way because we want the frag to have the
-        rl78_bytes in it, which we initialize above.  */
-      bytes = frag_more (olen);
+        rl78_bytes in it, which we initialize above.  The extra bytes
+        are for relaxing.  */
+      bytes = frag_more (olen + 3);
       frag_then = frag_now;
       frag_variant (rs_machine_dependent,
                    olen /* max_chars */,
@@ -638,13 +648,478 @@ rl78_cons_fix_new (fragS *       frag,
     }
 }
 
-/* No relaxation just yet */
+\f
+/*----------------------------------------------------------------------*/
+/* To recap: we estimate everything based on md_estimate_size, then
+   adjust based on rl78_relax_frag.  When it all settles, we call
+   md_convert frag to update the bytes.  The relaxation types and
+   relocations are in fragP->tc_frag_data, which is a copy of that
+   rl78_bytes.
+
+   Our scheme is as follows: fr_fix has the size of the smallest
+   opcode (like BRA.S).  We store the number of total bytes we need in
+   fr_subtype.  When we're done relaxing, we use fr_subtype and the
+   existing opcode bytes to figure out what actual opcode we need to
+   put in there.  If the fixup isn't resolvable now, we use the
+   maximal size.  */
+
+#define TRACE_RELAX 0
+#define tprintf if (TRACE_RELAX) printf
+
+
+typedef enum
+{
+  OT_other,
+  OT_bt,
+  OT_bt_sfr,
+  OT_bt_es,
+  OT_bc,
+  OT_bh
+} op_type_T;
+
+/* We're looking for these types of relaxations:
+
+   BT          00110001 sbit0cc1 addr----      (cc is 10 (BF) or 01 (BT))
+   B~T         00110001 sbit0cc1 00000011 11101110 pcrel16- -------- (BR $!pcrel20)
+
+   BT sfr      00110001 sbit0cc0 sfr----- addr----
+   BT ES:      00010001 00101110 sbit0cc1 addr----
+
+   BC          110111cc addr----
+   B~C         110111cc 00000011 11101110 pcrel16- -------- (BR $!pcrel20)
+
+   BH          01100001 110c0011 00000011 11101110 pcrel16- -------- (BR $!pcrel20)
+   B~H         01100001 110c0011 00000011 11101110 pcrel16- -------- (BR $!pcrel20)
+*/
+
+/* Given the opcode bytes at OP, figure out which opcode it is and
+   return the type of opcode.  We use this to re-encode the opcode as
+   a different size later.  */
+
+static op_type_T
+rl78_opcode_type (char * op)
+{
+  if (op[0] == 0x31
+      && ((op[1] & 0x0f) == 0x05
+         || (op[1] & 0x0f) == 0x03))
+    return OT_bt;
+
+  if (op[0] == 0x31
+      && ((op[1] & 0x0f) == 0x04
+         || (op[1] & 0x0f) == 0x02))
+    return OT_bt_sfr;
+
+  if (op[0] == 0x11
+      && op[1] == 0x31
+      && ((op[2] & 0x0f) == 0x05
+         || (op[2] & 0x0f) == 0x03))
+    return OT_bt_es;
+
+  if ((op[0] & 0xfc) == 0xdc)
+    return OT_bc;
+
+  if (op[0] == 0x61
+      && (op[1] & 0xef) == 0xc3)
+    return OT_bh;
+
+  return OT_other;
+}
+
+/* Returns zero if *addrP has the target address.  Else returns nonzero
+   if we cannot compute the target address yet.  */
+
+static int
+rl78_frag_fix_value (fragS *    fragP,
+                    segT       segment,
+                    int        which,
+                    addressT * addrP,
+                    int        need_diff,
+                    addressT * sym_addr)
+{
+  addressT addr = 0;
+  rl78_bytesT * b = fragP->tc_frag_data;
+  expressionS * exp = & b->fixups[which].exp;
+
+  if (need_diff && exp->X_op != O_subtract)
+    return 1;
+
+  if (exp->X_add_symbol)
+    {
+      if (S_FORCE_RELOC (exp->X_add_symbol, 1))
+       return 1;
+      if (S_GET_SEGMENT (exp->X_add_symbol) != segment)
+       return 1;
+      addr += S_GET_VALUE (exp->X_add_symbol);
+    }
+
+  if (exp->X_op_symbol)
+    {
+      if (exp->X_op != O_subtract)
+       return 1;
+      if (S_FORCE_RELOC (exp->X_op_symbol, 1))
+       return 1;
+      if (S_GET_SEGMENT (exp->X_op_symbol) != segment)
+       return 1;
+      addr -= S_GET_VALUE (exp->X_op_symbol);
+    }
+  if (sym_addr)
+    * sym_addr = addr;
+  addr += exp->X_add_number;
+  * addrP = addr;
+  return 0;
+}
+
+/* Estimate how big the opcode is after this relax pass.  The return
+   value is the difference between fr_fix and the actual size.  We
+   compute the total size in rl78_relax_frag and store it in fr_subtype,
+   sowe only need to subtract fx_fix and return it.  */
+
 int
 md_estimate_size_before_relax (fragS * fragP ATTRIBUTE_UNUSED, segT segment ATTRIBUTE_UNUSED)
 {
-  return 0;
+  int opfixsize;
+  int delta;
+
+  /* This is the size of the opcode that's accounted for in fr_fix.  */
+  opfixsize = fragP->fr_fix - (fragP->fr_opcode - fragP->fr_literal);
+  /* This is the size of the opcode that isn't.  */
+  delta = (fragP->fr_subtype - opfixsize);
+
+  tprintf (" -> opfixsize %d delta %d\n", opfixsize, delta);
+  return delta;
+}
+
+/* Given the new addresses for this relax pass, figure out how big
+   each opcode must be.  We store the total number of bytes needed in
+   fr_subtype.  The return value is the difference between the size
+   after the last pass and the size after this pass, so we use the old
+   fr_subtype to calculate the difference.  */
+
+int
+rl78_relax_frag (segT segment ATTRIBUTE_UNUSED, fragS * fragP, long stretch)
+{
+  addressT addr0, sym_addr;
+  addressT mypc;
+  int disp;
+  int oldsize = fragP->fr_subtype;
+  int newsize = oldsize;
+  op_type_T optype;
+  int ri;
+
+  mypc = fragP->fr_address + (fragP->fr_opcode - fragP->fr_literal);
+
+  /* If we ever get more than one reloc per opcode, this is the one
+     we're relaxing.  */
+  ri = 0;
+
+  optype = rl78_opcode_type (fragP->fr_opcode);
+  /* Try to get the target address.  */
+  if (rl78_frag_fix_value (fragP, segment, ri, & addr0,
+                          fragP->tc_frag_data->relax[ri].type != RL78_RELAX_BRANCH,
+                          & sym_addr))
+    {
+      /* If we don't, we must use the maximum size for the linker.  */
+      switch (fragP->tc_frag_data->relax[ri].type)
+       {
+       case RL78_RELAX_BRANCH:
+         switch (optype)
+           {
+           case OT_bt:
+             newsize = 6;
+             break;
+           case OT_bt_sfr:
+           case OT_bt_es:
+             newsize = 7;
+             break;
+           case OT_bc:
+             newsize = 5;
+             break;
+           case OT_bh:
+             newsize = 6;
+             break;
+           case OT_other:
+             newsize = oldsize;
+             break;
+           }
+         break;
+
+       }
+      fragP->fr_subtype = newsize;
+      tprintf (" -> new %d old %d delta %d (external)\n", newsize, oldsize, newsize-oldsize);
+      return newsize - oldsize;
+    }
+
+  if (sym_addr > mypc)
+    addr0 += stretch;
+
+  switch (fragP->tc_frag_data->relax[ri].type)
+    {
+    case  RL78_RELAX_BRANCH:
+      disp = (int) addr0 - (int) mypc;
+
+      switch (optype)
+       {
+       case OT_bt:
+         if (disp >= -128 && (disp - (oldsize-2)) <= 127)
+           newsize = 3;
+         else
+           newsize = 6;
+         break;
+       case OT_bt_sfr:
+       case OT_bt_es:
+         if (disp >= -128 && (disp - (oldsize-3)) <= 127)
+           newsize = 4;
+         else
+           newsize = 7;
+         break;
+       case OT_bc:
+         if (disp >= -128 && (disp - (oldsize-1)) <= 127)
+           newsize = 2;
+         else
+           newsize = 5;
+         break;
+       case OT_bh:
+         if (disp >= -128 && (disp - (oldsize-2)) <= 127)
+           newsize = 3;
+         else
+           newsize = 6;
+         break;
+       case OT_other:
+         newsize = oldsize;
+         break;
+       }
+      break;
+    }
+
+  /* This prevents infinite loops in align-heavy sources.  */
+  if (newsize < oldsize)
+    {
+      if (fragP->tc_frag_data->times_shrank > 10
+         && fragP->tc_frag_data->times_grown > 10)
+       newsize = oldsize;
+      if (fragP->tc_frag_data->times_shrank < 20)
+       fragP->tc_frag_data->times_shrank ++;
+    }
+  else if (newsize > oldsize)
+    {
+      if (fragP->tc_frag_data->times_grown < 20)
+       fragP->tc_frag_data->times_grown ++;
+    }
+
+  fragP->fr_subtype = newsize;
+  tprintf (" -> new %d old %d delta %d\n", newsize, oldsize, newsize-oldsize);
+  return newsize - oldsize;
+ }
+/* This lets us test for the opcode type and the desired size in a
+   switch statement.  */
+#define OPCODE(type,size) ((type) * 16 + (size))
+
+/* Given the opcode stored in fr_opcode and the number of bytes we
+   think we need, encode a new opcode.  We stored a pointer to the
+   fixup for this opcode in the tc_frag_data structure.  If we can do
+   the fixup here, we change the relocation type to "none" (we test
+   for that in tc_gen_reloc) else we change it to the right type for
+   the new (biggest) opcode.  */
+
+void
+md_convert_frag (bfd *   abfd ATTRIBUTE_UNUSED,
+                segT    segment ATTRIBUTE_UNUSED,
+                fragS * fragP ATTRIBUTE_UNUSED)
+{
+  rl78_bytesT * rl78b = fragP->tc_frag_data;
+  addressT addr0, mypc;
+  int disp;
+  int reloc_type, reloc_adjust;
+  char * op = fragP->fr_opcode;
+  int keep_reloc = 0;
+  int ri;
+  int fi = (rl78b->n_fixups > 1) ? 1 : 0;
+  fixS * fix = rl78b->fixups[fi].fixP;
+
+  /* If we ever get more than one reloc per opcode, this is the one
+     we're relaxing.  */
+  ri = 0;
+
+  /* We used a new frag for this opcode, so the opcode address should
+     be the frag address.  */
+  mypc = fragP->fr_address + (fragP->fr_opcode - fragP->fr_literal);
+  tprintf("\033[32mmypc: 0x%x\033[0m\n", (int)mypc);
+
+  /* Try to get the target address.  If we fail here, we just use the
+     largest format.  */
+  if (rl78_frag_fix_value (fragP, segment, 0, & addr0,
+                        fragP->tc_frag_data->relax[ri].type != RL78_RELAX_BRANCH, 0))
+    {
+      /* We don't know the target address.  */
+      keep_reloc = 1;
+      addr0 = 0;
+      disp = 0;
+      tprintf ("unknown addr ? - %x = ?\n", (int)mypc);
+    }
+  else
+    {
+      /* We know the target address, and it's in addr0.  */
+      disp = (int) addr0 - (int) mypc;
+      tprintf ("known addr %x - %x = %d\n", (int)addr0, (int)mypc, disp);
+    }
+
+  if (linkrelax)
+    keep_reloc = 1;
+
+  reloc_type = BFD_RELOC_NONE;
+  reloc_adjust = 0;
+
+  switch (fragP->tc_frag_data->relax[ri].type)
+    {
+    case RL78_RELAX_BRANCH:
+      switch (OPCODE (rl78_opcode_type (fragP->fr_opcode), fragP->fr_subtype))
+       {
+
+       case OPCODE (OT_bt, 3): /* BT A,$ - no change.  */
+         disp -= 3;
+         op[2] = disp;
+         break;
+
+       case OPCODE (OT_bt, 6): /* BT A,$ - long version.  */
+         disp -= 3;
+         op[1] ^= 0x06; /* toggle conditional.  */
+         op[2] = 3; /* displacement over long branch.  */
+         disp -= 3;
+         op[3] = 0xEE; /* BR $!addr20 */
+         op[4] = disp & 0xff;
+         op[5] = disp >> 8;
+         reloc_type = keep_reloc ? BFD_RELOC_16_PCREL : BFD_RELOC_NONE;
+         reloc_adjust = 2;
+         break;
+
+       case OPCODE (OT_bt_sfr, 4): /* BT PSW,$ - no change.  */
+         disp -= 4;
+         op[3] = disp;
+         break;
+
+       case OPCODE (OT_bt_sfr, 7): /* BT PSW,$ - long version.  */
+         disp -= 4;
+         op[1] ^= 0x06; /* toggle conditional.  */
+         op[3] = 3; /* displacement over long branch.  */
+         disp -= 3;
+         op[4] = 0xEE; /* BR $!addr20 */
+         op[5] = disp & 0xff;
+         op[6] = disp >> 8;
+         reloc_type = keep_reloc ? BFD_RELOC_16_PCREL : BFD_RELOC_NONE;
+         reloc_adjust = 2;
+         break;
+
+       case OPCODE (OT_bt_es, 4): /* BT ES:[HL],$ - no change.  */
+         disp -= 4;
+         op[3] = disp;
+         break;
+
+       case OPCODE (OT_bt_es, 7): /* BT PSW,$ - long version.  */
+         disp -= 4;
+         op[2] ^= 0x06; /* toggle conditional.  */
+         op[3] = 3; /* displacement over long branch.  */
+         disp -= 3;
+         op[4] = 0xEE; /* BR $!addr20 */
+         op[5] = disp & 0xff;
+         op[6] = disp >> 8;
+         reloc_type = keep_reloc ? BFD_RELOC_16_PCREL : BFD_RELOC_NONE;
+         reloc_adjust = 2;
+         break;
+
+       case OPCODE (OT_bc, 2): /* BC $ - no change.  */
+         disp -= 2;
+         op[1] = disp;
+         break;
+
+       case OPCODE (OT_bc, 5): /* BC $ - long version.  */
+         disp -= 2;
+         op[0] ^= 0x02; /* toggle conditional.  */
+         op[1] = 3;
+         disp -= 3;
+         op[2] = 0xEE; /* BR $!addr20 */
+         op[3] = disp & 0xff;
+         op[4] = disp >> 8;
+         reloc_type = keep_reloc ? BFD_RELOC_16_PCREL : BFD_RELOC_NONE;
+         reloc_adjust = 2;
+         break;
+
+       case OPCODE (OT_bh, 3): /* BH $ - no change.  */
+         disp -= 3;
+         op[2] = disp;
+         break;
+
+       case OPCODE (OT_bh, 6): /* BC $ - long version.  */
+         disp -= 3;
+         op[1] ^= 0x10; /* toggle conditional.  */
+         op[2] = 3;
+         disp -= 3;
+         op[3] = 0xEE; /* BR $!addr20 */
+         op[4] = disp & 0xff;
+         op[5] = disp >> 8;
+         reloc_type = keep_reloc ? BFD_RELOC_16_PCREL : BFD_RELOC_NONE;
+         reloc_adjust = 2;
+         break;
+
+       default:
+         fprintf(stderr, "Missed case %d %d at 0x%lx\n",
+                 rl78_opcode_type (fragP->fr_opcode), fragP->fr_subtype, mypc);
+         abort ();
+         
+       }
+      break;
+
+    default:
+      if (rl78b->n_fixups)
+       {
+         reloc_type = fix->fx_r_type;
+         reloc_adjust = 0;
+       }
+      break;
+    }
+
+  if (rl78b->n_fixups)
+    {
+
+      fix->fx_r_type = reloc_type;
+      fix->fx_where += reloc_adjust;
+      switch (reloc_type)
+       {
+       case BFD_RELOC_NONE:
+         fix->fx_size = 0;
+         break;
+       case BFD_RELOC_8:
+         fix->fx_size = 1;
+         break;
+       case BFD_RELOC_16_PCREL:
+         fix->fx_size = 2;
+         break;
+       }
+    }
+
+  fragP->fr_fix = fragP->fr_subtype + (fragP->fr_opcode - fragP->fr_literal);
+  tprintf ("fragP->fr_fix now %ld (%d + (%p - %p)\n", (long) fragP->fr_fix,
+         fragP->fr_subtype, fragP->fr_opcode, fragP->fr_literal);
+  fragP->fr_var = 0;
+
+  tprintf ("compare 0x%lx vs 0x%lx - 0x%lx = 0x%lx (%p)\n",
+          (long)fragP->fr_fix,
+          (long)fragP->fr_next->fr_address, (long)fragP->fr_address,
+          (long)(fragP->fr_next->fr_address - fragP->fr_address),
+          fragP->fr_next);
+
+  if (fragP->fr_next != NULL
+         && ((offsetT) (fragP->fr_next->fr_address - fragP->fr_address)
+             != fragP->fr_fix))
+    as_bad (_("bad frag at %p : fix %ld addr %ld %ld \n"), fragP,
+           (long) fragP->fr_fix,
+           (long) fragP->fr_address, (long) fragP->fr_next->fr_address);
 }
 
+/* End of relaxation code.
+  ----------------------------------------------------------------------*/
+\f
+
 arelent **
 tc_gen_reloc (asection * seg ATTRIBUTE_UNUSED, fixS * fixp)
 {
@@ -867,12 +1342,3 @@ md_section_align (segT segment, valueT size)
   int align = bfd_get_section_alignment (stdoutput, segment);
   return ((size + (1 << align) - 1) & (-1 << align));
 }
-
-void
-md_convert_frag (bfd *   abfd ATTRIBUTE_UNUSED,
-                segT    segment ATTRIBUTE_UNUSED,
-                fragS * fragP ATTRIBUTE_UNUSED)
-{
-  /* No relaxation yet */
-  fragP->fr_var = 0;
-}
index e7fd78252cedd2bb811b6055699b369dca7cf95f..2621ab6448aebc8f32ecb1a0a4716b1e91bba6a0 100644 (file)
@@ -49,6 +49,9 @@ extern int target_little_endian;
 #define md_end rl78_md_end
 extern void rl78_md_end (void);
 
+#define md_relax_frag rl78_relax_frag
+extern int rl78_relax_frag (segT, fragS *, long);
+
 #define TC_FRAG_TYPE struct rl78_bytesT *
 #define TC_FRAG_INIT rl78_frag_init
 extern void rl78_frag_init (fragS *);