This is the mail archive of the binutils@sourceware.org mailing list for the binutils project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] FT32: support for FT32B processor - part 2/2


FT32B is a new FT32 family member. It has a code
compression scheme, which requires the use of linker
relaxations. The change is quite large, so submission
is in several parts.

Part 2 adds a relaxation pass, which actually implements
the code compression scheme.

OK to commit?

James.

bfd/ChangeLog:

2017-10-26  James Bowman  <james.bowman@ftdichip.com>

	* archures.c: Add bfd_mach_ft32b.
	* cpu-ft32.c: Add arch_info_struct.
	* elf32-ft32.c: Add R_FT32_RELAX, SC0, SC1,
	DIFF32. (ft32_elf_relocate_section): Add clauses
	for R_FT32_SC0, SC1, DIFF32.  (ft32_reloc_shortable,
	elf32_ft32_is_diff_reloc, elf32_ft32_adjust_diff_reloc_value,
	elf32_ft32_adjust_reloc_if_spans_insn,
	elf32_ft32_relax_delete_bytes, elf32_ft32_relax_is_branch_target,
	ft32_elf_relax_section): New function.
	* reloc.c: Add BFD_RELOC_FT32_RELAX, SC0, SC1, DIFF32.
	* bfd-in2.h: Regenerate.
	* libbfd.h: Regenerate.

gas/ChangeLog:

2017-10-26  James Bowman  <james.bowman@ftdichip.com>

	* config/tc-ft32.c (md_assemble): add relaxation reloc
	BFD_RELOC_FT32_RELAX.  (md_longopts): Add "norelax" and
	"no-relax". (md_apply_fix): Add reloc BFD_RELOC_FT32_DIFF32.
	(relaxable_section, ft32_validate_fix_sub, ft32_force_relocation,
	ft32_allow_local_subtract): New function.
	* config/tc-ft32.h: remove unused MD_PCREL_FROM_SECTION.

include/ChangeLog:

2017-10-26  James Bowman  <james.bowman@ftdichip.com>

	* elf/ft32.h: Add R_FT32_RELAX, SC0, SC1, DIFF32.

---
diff --git a/bfd/archures.c b/bfd/archures.c
index 433b95f..bd5f933 100644
--- a/bfd/archures.c
+++ b/bfd/archures.c
@@ -406,6 +406,7 @@ DESCRIPTION
 .#define bfd_mach_moxie		1
 .  bfd_arch_ft32,       {* The ft32 processor *}
 .#define bfd_mach_ft32		1
+.#define bfd_mach_ft32b		2
 .  bfd_arch_mcore,
 .  bfd_arch_mep,
 .#define bfd_mach_mep		1
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 503e237..1b483bd 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -2265,6 +2265,7 @@ enum bfd_architecture
 #define bfd_mach_moxie         1
   bfd_arch_ft32,       /* The ft32 processor */
 #define bfd_mach_ft32          1
+#define bfd_mach_ft32b         2
   bfd_arch_mcore,
   bfd_arch_mep,
 #define bfd_mach_mep           1
@@ -3149,7 +3150,11 @@ to compensate for the borrow when the low bits are added.  */
   BFD_RELOC_FT32_20,
   BFD_RELOC_FT32_17,
   BFD_RELOC_FT32_18,
+  BFD_RELOC_FT32_RELAX,
+  BFD_RELOC_FT32_SC0,
+  BFD_RELOC_FT32_SC1,
   BFD_RELOC_FT32_15,
+  BFD_RELOC_FT32_DIFF32,
 
 
 /* Fujitsu Frv Relocations.  */
diff --git a/bfd/cpu-ft32.c b/bfd/cpu-ft32.c
index b24b0fc..033720b 100644
--- a/bfd/cpu-ft32.c
+++ b/bfd/cpu-ft32.c
@@ -23,6 +23,40 @@
 #include "libbfd.h"
 
 
+static const bfd_arch_info_type arch_info_struct[] =
+  {
+    {
+      32,               /* 32 bits in a word.  */
+      32,               /* 32 bits in an address.  */
+      8,                /*  8 bits in a byte.  */
+      bfd_arch_ft32,   /* enum bfd_architecture arch.  */
+      bfd_mach_ft32,
+      "ft32",          /* Arch name.  */
+      "ft32",          /* Printable name.  */
+      2,                /* Unsigned int section alignment power.  */
+      FALSE,             /* The one and only.  */
+      bfd_default_compatible,
+      bfd_default_scan,
+      bfd_arch_default_fill,
+      &arch_info_struct[1],
+    },
+    {
+      32,               /* 32 bits in a word.  */
+      32,               /* 32 bits in an address.  */
+      8,                /*  8 bits in a byte.  */
+      bfd_arch_ft32,   /* enum bfd_architecture arch.  */
+      bfd_mach_ft32b,
+      "ft32b",          /* Arch name.  */
+      "ft32b",          /* Printable name.  */
+      2,                /* Unsigned int section alignment power.  */
+      FALSE,            /* The one and only.  */
+      bfd_default_compatible,
+      bfd_default_scan,
+      bfd_arch_default_fill,
+      0,
+    },
+  };
+
 const bfd_arch_info_type bfd_ft32_arch =
   {
     32,               /* 32 bits in a word.  */
@@ -37,5 +71,5 @@ const bfd_arch_info_type bfd_ft32_arch =
     bfd_default_compatible,
     bfd_default_scan,
     bfd_arch_default_fill,
-    0,
+    arch_info_struct,
   };
diff --git a/bfd/elf32-ft32.c b/bfd/elf32-ft32.c
index 2ee691c..32f913f 100644
--- a/bfd/elf32-ft32.c
+++ b/bfd/elf32-ft32.c
@@ -27,8 +27,13 @@
 #include "libbfd.h"
 #include "elf-bfd.h"
 #include "elf/ft32.h"
+#include "opcode/ft32.h"
 
-/* Forward declarations.  */
+static bfd_boolean debug_relax = FALSE;
+
+static bfd_reloc_status_type
+bfd_elf_ft32_diff_reloc (bfd *, arelent *, asymbol *, void *,
+			asection *, bfd *, char **);
 
 static reloc_howto_type ft32_elf_howto_table [] =
 {
@@ -69,7 +74,7 @@ static reloc_howto_type ft32_elf_howto_table [] =
 	 16,			/* bitsize */
 	 FALSE,			/* pc_relative */
 	 0,			/* bitpos */
-	 complain_overflow_bitfield, /* complain_on_overflow */
+	 complain_overflow_dont, /* complain_on_overflow */
 	 bfd_elf_generic_reloc,	/* special_function */
 	 "R_FT32_16",		/* name */
 	 FALSE,			/* partial_inplace */
@@ -97,7 +102,7 @@ static reloc_howto_type ft32_elf_howto_table [] =
 	 10,			/* bitsize */
 	 FALSE,			/* pc_relative */
 	 4,			/* bitpos */
-	 complain_overflow_signed, /* complain_on_overflow */
+	 complain_overflow_bitfield, /* complain_on_overflow */
 	 bfd_elf_generic_reloc,	/* special_function */
 	 "R_FT32_10",		/* name */
 	 FALSE,			/* partial_inplace */
@@ -142,11 +147,51 @@ static reloc_howto_type ft32_elf_howto_table [] =
 	 complain_overflow_dont, /* complain_on_overflow */
 	 bfd_elf_generic_reloc,	/* special_function */
 	 "R_FT32_18",		/* name */
-	 FALSE,		/* partial_inplace */
+	 FALSE,			/* partial_inplace */
 	 0x00000000,		/* src_mask */
 	 0x0003ffff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
 
+  HOWTO (R_FT32_RELAX,		/* type */
+	 0,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 10,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 4,			/* bitpos */
+	 complain_overflow_signed, /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	/* special_function */
+	 "R_FT32_RELAX",	/* name */
+	 FALSE,			/* partial_inplace */
+	 0x00000000,		/* src_mask */
+	 0x00000000,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
+
+  HOWTO (R_FT32_SC0,		/* type */
+	 0,			/* rightshift */
+	 1,			/* size (0 = byte, 1 = short, 2 = long) */
+	 10,			/* bitsize */
+	 FALSE,			/* pc_relative */
+	 4,			/* bitpos */
+	 complain_overflow_signed, /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	/* special_function */
+	 "R_FT32_SC0",	        /* name */
+	 FALSE,			/* partial_inplace */
+	 0x00000000,		/* src_mask */
+	 0x00000000,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
+  HOWTO (R_FT32_SC1,		/* type */
+	 2,			/* rightshift */
+	 2,			/* size (0 = byte, 1 = short, 2 = long) */
+	 22,			/* bitsize */
+	 TRUE,			/* pc_relative */
+	 7,			/* bitpos */
+	 complain_overflow_dont, /* complain_on_overflow */
+	 bfd_elf_generic_reloc,	/* special_function */
+	 "R_FT32_SC1",	        /* name */
+	 TRUE,			/* partial_inplace */
+	 0x07ffff80,		/* src_mask */
+	 0x07ffff80,		/* dst_mask */
+	 FALSE),		/* pcrel_offset */
   HOWTO (R_FT32_15,		/* type */
 	 0,			/* rightshift */
 	 2,			/* size (0 = byte, 1 = short, 2 = long) */
@@ -160,8 +205,22 @@ static reloc_howto_type ft32_elf_howto_table [] =
 	 0x00000000,		/* src_mask */
 	 0x00007fff,		/* dst_mask */
 	 FALSE),		/* pcrel_offset */
+  HOWTO (R_FT32_DIFF32,  	/* type */
+	 0,             	/* rightshift */
+	 2,         		/* size (0 = byte, 1 = short, 2 = long) */
+	 32,        		/* bitsize */
+	 FALSE,         	/* pc_relative */
+	 0,             	/* bitpos */
+	 complain_overflow_dont, /* complain_on_overflow */
+	 bfd_elf_ft32_diff_reloc, /* special_function */
+	 "R_FT32_DIFF32",     	/* name */
+	 FALSE,         	/* partial_inplace */
+	 0,             	/* src_mask */
+	 0xffffffff,    	/* dst_mask */
+	 FALSE),        	/* pcrel_offset */
 };
 
+
 /* Map BFD reloc types to FT32 ELF reloc types.  */
 
 struct ft32_reloc_map
@@ -172,17 +231,38 @@ struct ft32_reloc_map
 
 static const struct ft32_reloc_map ft32_reloc_map [] =
 {
-  { BFD_RELOC_NONE,            R_FT32_NONE },
-  { BFD_RELOC_32,              R_FT32_32 },
-  { BFD_RELOC_16,              R_FT32_16 },
-  { BFD_RELOC_8,               R_FT32_8 },
-  { BFD_RELOC_FT32_10,           R_FT32_10 },
-  { BFD_RELOC_FT32_20,           R_FT32_20 },
-  { BFD_RELOC_FT32_17,           R_FT32_17 },
-  { BFD_RELOC_FT32_18,           R_FT32_18 },
+  { BFD_RELOC_NONE,             R_FT32_NONE },
+  { BFD_RELOC_32,               R_FT32_32 },
+  { BFD_RELOC_16,               R_FT32_16 },
+  { BFD_RELOC_8,                R_FT32_8 },
+  { BFD_RELOC_FT32_10,          R_FT32_10 },
+  { BFD_RELOC_FT32_20,          R_FT32_20 },
+  { BFD_RELOC_FT32_17,          R_FT32_17 },
+  { BFD_RELOC_FT32_18,          R_FT32_18 },
+  { BFD_RELOC_FT32_RELAX,       R_FT32_RELAX },
+  { BFD_RELOC_FT32_SC0,         R_FT32_SC0 },
+  { BFD_RELOC_FT32_SC1,         R_FT32_SC1 },
   { BFD_RELOC_FT32_15,          R_FT32_15 },
+  { BFD_RELOC_FT32_DIFF32,      R_FT32_DIFF32 },
 };
 
+/* Perform a diff relocation. Nothing to do, as the difference value is
+   already written into the section's contents. */
+
+static bfd_reloc_status_type
+bfd_elf_ft32_diff_reloc (bfd *abfd ATTRIBUTE_UNUSED,
+		      arelent *reloc_entry ATTRIBUTE_UNUSED,
+	      asymbol *symbol ATTRIBUTE_UNUSED,
+	      void *data ATTRIBUTE_UNUSED,
+	      asection *input_section ATTRIBUTE_UNUSED,
+	      bfd *output_bfd ATTRIBUTE_UNUSED,
+	      char **error_message ATTRIBUTE_UNUSED)
+{
+  return bfd_reloc_ok;
+}
+
+
+
 static reloc_howto_type *
 ft32_reloc_type_lookup (bfd *abfd ATTRIBUTE_UNUSED,
 			 bfd_reloc_code_real_type code)
@@ -323,9 +403,62 @@ ft32_elf_relocate_section (bfd *output_bfd,
       if (bfd_link_relocatable (info))
 	continue;
 
-      r = _bfd_final_link_relocate (howto, input_bfd, input_section,
-				    contents, rel->r_offset,
-				    relocation, rel->r_addend);
+      switch (howto->type)
+	{
+	  case R_FT32_SC0:
+	    {
+	      unsigned int insn;
+	      int offset;
+	      unsigned int code15[2];
+
+	      insn = bfd_get_32 (input_bfd, contents + rel->r_offset);
+	      ft32_split_shortcode (insn, code15);
+
+	      offset = (int)relocation;
+	      offset += (int)(rel->r_addend - rel->r_offset);
+	      offset -= (input_section->output_section->vma +
+			 input_section->output_offset);
+	      if ((offset < -1024) || (offset >= 1024))
+		abort ();
+	      code15[0] |= ((offset / 4) & 511);
+	      insn = ft32_merge_shortcode (code15);
+	      bfd_put_32 (input_bfd, insn, contents + rel->r_offset);
+	    }
+	    r = bfd_reloc_ok;
+	    break;
+
+	  case R_FT32_SC1:
+	    {
+	      unsigned int insn;
+	      int offset;
+	      unsigned int code15[2];
+
+	      insn = bfd_get_32 (input_bfd, contents + rel->r_offset);
+	      ft32_split_shortcode (insn, code15);
+
+	      offset = (int)relocation;
+	      offset += (int)(rel->r_addend - rel->r_offset);
+	      offset -= (input_section->output_section->vma +
+			 input_section->output_offset);
+	      if ((offset < -1024) || (offset >= 1024))
+		abort ();
+	      code15[1] |= ((offset / 4) & 511);
+	      insn = ft32_merge_shortcode (code15);
+	      bfd_put_32 (input_bfd, insn, contents + rel->r_offset);
+	    }
+	    r = bfd_reloc_ok;
+	    break;
+
+	  case R_FT32_DIFF32:
+	    r = bfd_reloc_ok;
+	    break;
+
+	  default:
+	    r = _bfd_final_link_relocate (howto, input_bfd, input_section,
+					  contents, rel->r_offset,
+					  relocation, rel->r_addend);
+	    break;
+	}
 
       if (r != bfd_reloc_ok)
 	{
@@ -345,19 +478,19 @@ ft32_elf_relocate_section (bfd *output_bfd,
 	      break;
 
 	    case bfd_reloc_outofrange:
-	      msg = _("internal error: out of range error");
+	      msg = _ ("internal error: out of range error");
 	      break;
 
 	    case bfd_reloc_notsupported:
-	      msg = _("internal error: unsupported relocation error");
+	      msg = _ ("internal error: unsupported relocation error");
 	      break;
 
 	    case bfd_reloc_dangerous:
-	      msg = _("internal error: dangerous relocation");
+	      msg = _ ("internal error: dangerous relocation");
 	      break;
 
 	    default:
-	      msg = _("internal error: unknown error");
+	      msg = _ ("internal error: unknown error");
 	      break;
 	    }
 
@@ -370,6 +503,739 @@ ft32_elf_relocate_section (bfd *output_bfd,
   return TRUE;
 }
 
+/* Relaxation.  */
+
+static bfd_boolean
+ft32_reloc_shortable
+    (bfd *                  abfd,
+     asection *             sec,
+     Elf_Internal_Sym *     isymbuf ATTRIBUTE_UNUSED,
+     bfd_byte *             contents,
+     bfd_vma                pc ATTRIBUTE_UNUSED,
+     Elf_Internal_Rela *    irel,
+     unsigned int *         sc)
+{
+  Elf_Internal_Shdr *symtab_hdr ATTRIBUTE_UNUSED;
+  bfd_vma symval;
+
+  enum elf_ft32_reloc_type r_type;
+  reloc_howto_type *howto = NULL;
+  unsigned int insn;
+  int offset;
+  bfd_vma dot, value;
+
+  r_type = ELF32_R_TYPE (irel->r_info);
+  howto = &ft32_elf_howto_table [r_type];
+
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+
+  /* Get the value of the symbol referred to by the reloc.  */
+  if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
+    {
+      /* A local symbol.  */
+      Elf_Internal_Sym *isym;
+      asection *sym_sec;
+
+      isym = isymbuf + ELF32_R_SYM (irel->r_info);
+      sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+      symval = isym->st_value;
+      /* If the reloc is absolute, it will not have
+	 a symbol or section associated with it.  */
+      if (sym_sec)
+	symval += sym_sec->output_section->vma
+	  + sym_sec->output_offset;
+    }
+  else
+    {
+      unsigned long indx;
+      struct elf_link_hash_entry *h;
+
+      /* An external symbol.  */
+      indx = ELF32_R_SYM (irel->r_info) - symtab_hdr->sh_info;
+      h = elf_sym_hashes (abfd)[indx];
+      BFD_ASSERT (h != NULL);
+      if (h->root.type != bfd_link_hash_defined
+	  && h->root.type != bfd_link_hash_defweak)
+	/* This appears to be a reference to an undefined
+	   symbol.  Just ignore it--it will be caught by the
+	   regular reloc processing.  */
+	return 0;
+
+      symval = (h->root.u.def.value
+		+ h->root.u.def.section->output_section->vma
+		+ h->root.u.def.section->output_offset);
+    }
+
+  switch (r_type)
+    {
+      case R_FT32_8:
+      case R_FT32_10:
+      case R_FT32_16:
+      case R_FT32_20:
+      case R_FT32_RELAX:
+	if (symval != 0)
+	  return 0;
+	insn = bfd_get_32 (abfd, contents + irel->r_offset);
+	insn |= ((symval + irel->r_addend) << howto->bitpos) & howto->dst_mask;
+	return ft32_shortcode (insn, sc);
+
+      case R_FT32_18:
+	insn = bfd_get_32 (abfd, contents + irel->r_offset);
+	/* Get the address of this instruction.  */
+	dot = (sec->output_section->vma
+	       + sec->output_offset + irel->r_offset);
+	value = symval + irel->r_addend;
+	offset = (value - dot) / 4;
+
+	if ((dot > 0x8c) && (-256 <= offset) && (offset < 256))
+	  {
+	    switch (insn)
+	      {
+		case 0x00200000: *sc = (3 << 13) | (0  << 9); return 1;
+		case 0x00280000: *sc = (3 << 13) | (1  << 9); return 1;
+		case 0x00600000: *sc = (3 << 13) | (2  << 9); return 1;
+		case 0x00680000: *sc = (3 << 13) | (3  << 9); return 1;
+		case 0x00a00000: *sc = (3 << 13) | (4  << 9); return 1;
+		case 0x00a80000: *sc = (3 << 13) | (5  << 9); return 1;
+		case 0x00e00000: *sc = (3 << 13) | (6  << 9); return 1;
+		case 0x00e80000: *sc = (3 << 13) | (7  << 9); return 1;
+		case 0x01200000: *sc = (3 << 13) | (8  << 9); return 1;
+		case 0x01280000: *sc = (3 << 13) | (9  << 9); return 1;
+		case 0x01600000: *sc = (3 << 13) | (10 << 9); return 1;
+		case 0x01680000: *sc = (3 << 13) | (11 << 9); return 1;
+		case 0x01a00000: *sc = (3 << 13) | (12 << 9); return 1;
+		case 0x01a80000: *sc = (3 << 13) | (13 << 9); return 1;
+
+		case 0x00300000: *sc = (3 << 13) | (14 << 9); return 1;
+		case 0x00340000: *sc = (3 << 13) | (15 << 9); return 1;
+
+		default:
+		  break;
+	      }
+	  }
+	break;
+
+      default:
+	break;
+    }
+  return 0;
+}
+
+/* Returns whether the relocation type passed is a diff reloc. */
+
+static bfd_boolean
+elf32_ft32_is_diff_reloc (Elf_Internal_Rela *irel)
+{
+  return (ELF32_R_TYPE (irel->r_info) == R_FT32_DIFF32);
+}
+
+/* Reduce the diff value written in the section by count if the shrinked
+   insn address happens to fall between the two symbols for which this
+   diff reloc was emitted.  */
+
+static void
+elf32_ft32_adjust_diff_reloc_value (bfd *abfd,
+				   struct bfd_section *isec,
+				   Elf_Internal_Rela *irel,
+				   bfd_vma symval,
+				   bfd_vma shrinked_insn_address,
+				   int count)
+{
+  unsigned char *reloc_contents = NULL;
+  unsigned char *isec_contents = elf_section_data (isec)->this_hdr.contents;
+  if (isec_contents == NULL)
+  {
+    if (! bfd_malloc_and_get_section (abfd, isec, &isec_contents))
+      return;
+
+    elf_section_data (isec)->this_hdr.contents = isec_contents;
+  }
+
+  reloc_contents = isec_contents + irel->r_offset;
+
+  /* Read value written in object file. */
+  bfd_signed_vma x = 0;
+  switch (ELF32_R_TYPE (irel->r_info))
+  {
+  case R_FT32_DIFF32:
+    {
+      x = bfd_get_signed_32 (abfd, reloc_contents);
+      break;
+    }
+  default:
+    {
+      BFD_FAIL();
+    }
+  }
+
+  /* For a diff reloc sym1 - sym2 the diff at assembly time (x) is written
+     into the object file at the reloc offset. sym2's logical value is
+     symval (<start_of_section>) + reloc addend. Compute the start and end
+     addresses and check if the shrinked insn falls between sym1 and sym2. */
+
+  bfd_vma sym2_address = symval + irel->r_addend;
+  bfd_vma sym1_address = sym2_address - x;
+
+  /* Don't assume sym2 is bigger than sym1 - the difference
+     could be negative. Compute start and end addresses, and
+     use those to see if they span shrinked_insn_address. */
+
+  bfd_vma start_address = sym1_address < sym2_address
+    ? sym1_address : sym2_address;
+  bfd_vma end_address = sym1_address > sym2_address
+    ? sym1_address : sym2_address;
+
+
+  if (shrinked_insn_address >= start_address
+      && shrinked_insn_address < end_address)
+  {
+    /* Reduce the diff value by count bytes and write it back into section
+       contents. */
+    bfd_signed_vma new_diff = x < 0 ? x + count : x - count;
+
+    if (sym2_address > shrinked_insn_address)
+      irel->r_addend -= count;
+
+    switch (ELF32_R_TYPE (irel->r_info))
+    {
+    case R_FT32_DIFF32:
+      {
+	bfd_put_signed_32 (abfd, new_diff & 0xFFFFFFFF, reloc_contents);
+	break;
+      }
+    default:
+      {
+	BFD_FAIL();
+      }
+    }
+
+  }
+}
+
+static void
+elf32_ft32_adjust_reloc_if_spans_insn (bfd *abfd,
+				      asection *isec,
+				      Elf_Internal_Rela *irel,  bfd_vma symval,
+				      bfd_vma shrinked_insn_address,
+				      bfd_vma shrink_boundary,
+				      int count)
+{
+
+  if (elf32_ft32_is_diff_reloc (irel))
+    {
+      elf32_ft32_adjust_diff_reloc_value (abfd, isec, irel,
+					 symval,
+					 shrinked_insn_address,
+					 count);
+    }
+  else
+    {
+      bfd_vma reloc_value = symval + irel->r_addend;
+      bfd_boolean addend_within_shrink_boundary =
+	(reloc_value <= shrink_boundary);
+
+      bfd_boolean reloc_spans_insn =
+	(symval <= shrinked_insn_address
+	 && reloc_value > shrinked_insn_address
+	 && addend_within_shrink_boundary);
+
+      if (! reloc_spans_insn)
+	return;
+
+      irel->r_addend -= count;
+
+      if (debug_relax)
+	printf ("Relocation's addend needed to be fixed \n");
+    }
+}
+
+/* Delete some bytes from a section while relaxing.  */
+
+static bfd_boolean
+elf32_ft32_relax_delete_bytes (struct bfd_link_info *link_info, bfd * abfd,
+			       asection * sec, bfd_vma addr, int count)
+{
+  Elf_Internal_Shdr *symtab_hdr;
+  unsigned int sec_shndx;
+  bfd_byte *contents;
+  Elf_Internal_Rela *irel, *irelend;
+  bfd_vma toaddr;
+  Elf_Internal_Sym *isym;
+  Elf_Internal_Sym *isymend;
+  struct elf_link_hash_entry **sym_hashes;
+  struct elf_link_hash_entry **end_hashes;
+  struct elf_link_hash_entry **start_hashes;
+  unsigned int symcount;
+  Elf_Internal_Sym *isymbuf = NULL;
+
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+  sec_shndx = _bfd_elf_section_from_bfd_section (abfd, sec);
+
+  contents = elf_section_data (sec)->this_hdr.contents;
+
+  toaddr = sec->size;
+
+  irel = elf_section_data (sec)->relocs;
+  irelend = irel + sec->reloc_count;
+
+  /* Actually delete the bytes.  */
+  memmove (contents + addr, contents + addr + count,
+	   (size_t) (toaddr - addr - count));
+  sec->size -= count;
+
+  /* Adjust all the relocs.  */
+  for (irel = elf_section_data (sec)->relocs; irel < irelend; irel++)
+    /* Get the new reloc address.  */
+    if ((irel->r_offset > addr && irel->r_offset < toaddr))
+      {
+	irel->r_offset -= count;
+      }
+
+  /* The reloc's own addresses are now ok. However, we need to readjust
+     the reloc's addend, i.e. the reloc's value if two conditions are met:
+     1.) the reloc is relative to a symbol in this section that
+     is located in front of the shrinked instruction
+     2.) symbol plus addend end up behind the shrinked instruction.
+
+     The most common case where this happens are relocs relative to
+     the section-start symbol.
+
+     This step needs to be done for all of the sections of the bfd.  */
+
+  {
+    struct bfd_section *isec;
+
+    for (isec = abfd->sections; isec; isec = isec->next)
+      {
+	bfd_vma symval;
+	bfd_vma shrinked_insn_address;
+
+	if (isec->reloc_count == 0)
+	  continue;
+
+	shrinked_insn_address = (sec->output_section->vma
+				 + sec->output_offset + addr - count);
+
+	irel = elf_section_data (isec)->relocs;
+	/* PR 12161: Read in the relocs for this section if necessary.  */
+	if (irel == NULL)
+	  irel = _bfd_elf_link_read_relocs (abfd, isec, NULL, NULL, TRUE);
+
+	for (irelend = irel + isec->reloc_count; irel < irelend; irel++)
+	  {
+	    /* Read this BFD's local symbols if we haven't done
+	       so already.  */
+	    if (isymbuf == NULL && symtab_hdr->sh_info != 0)
+	      {
+		isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
+		if (isymbuf == NULL)
+		  isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr,
+						  symtab_hdr->sh_info, 0,
+						  NULL, NULL, NULL);
+		if (isymbuf == NULL)
+		  return FALSE;
+	      }
+
+	    /* Get the value of the symbol referred to by the reloc.  */
+	    if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
+	      {
+		/* A local symbol.  */
+		asection *sym_sec;
+
+		isym = isymbuf + ELF32_R_SYM (irel->r_info);
+		sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+		symval = isym->st_value;
+		/* If the reloc is absolute, it will not have
+		   a symbol or section associated with it.  */
+		if (sym_sec == sec)
+		  {
+		    symval += sym_sec->output_section->vma
+		      + sym_sec->output_offset;
+
+		    if (debug_relax)
+		      printf ("Checking if the relocation's "
+			      "addend needs corrections.\n"
+			      "Address of anchor symbol: 0x%x \n"
+			      "Address of relocation target: 0x%x \n"
+			      "Address of relaxed insn: 0x%x \n",
+			      (unsigned int) symval,
+			      (unsigned int) (symval + irel->r_addend),
+			      (unsigned int) shrinked_insn_address);
+
+		    if (symval <= shrinked_insn_address
+			&& (symval + irel->r_addend) > shrinked_insn_address)
+		      {
+			/* If there is an alignment boundary, we only need to
+			   adjust addends that end up below the boundary. */
+			bfd_vma shrink_boundary = (toaddr
+						   + sec->output_section->vma
+						   + sec->output_offset);
+
+			if (debug_relax)
+			  printf
+			    ("Relocation's addend needed to be fixed \n");
+
+			elf32_ft32_adjust_reloc_if_spans_insn (abfd, isec,
+							       irel, symval,
+							       shrinked_insn_address,
+							       shrink_boundary,
+							       count);
+		      }
+		  }
+		/* else reference symbol is absolute. No adjustment needed. */
+	      }
+	    /* else...Reference symbol is extern.  No need for adjusting
+	       the addend.  */
+	  }
+      }
+  }
+
+
+  /* Adjust the local symbols defined in this section.  */
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+  isym = (Elf_Internal_Sym *) symtab_hdr->contents;
+  if (isym)
+    {
+      for (isymend = isym + symtab_hdr->sh_info; isym < isymend; isym++)
+	{
+	  if (isym->st_shndx == sec_shndx
+	      && isym->st_value > addr && isym->st_value < toaddr)
+	    isym->st_value -= count;
+	}
+    }
+
+  /* Now adjust the global symbols defined in this section.  */
+  symcount = (symtab_hdr->sh_size / sizeof (Elf32_External_Sym)
+	      - symtab_hdr->sh_info);
+  sym_hashes = start_hashes = elf_sym_hashes (abfd);
+  end_hashes = sym_hashes + symcount;
+
+  for (; sym_hashes < end_hashes; sym_hashes++)
+    {
+      struct elf_link_hash_entry *sym_hash = *sym_hashes;
+
+      /* The '--wrap SYMBOL' option is causing a pain when the object file,
+         containing the definition of __wrap_SYMBOL, includes a direct
+         call to SYMBOL as well. Since both __wrap_SYMBOL and SYMBOL reference
+         the same symbol (which is __wrap_SYMBOL), but still exist as two
+         different symbols in 'sym_hashes', we don't want to adjust
+         the global symbol __wrap_SYMBOL twice.
+         This check is only relevant when symbols are being wrapped.  */
+      if (link_info->wrap_hash != NULL)
+	{
+	  struct elf_link_hash_entry **cur_sym_hashes;
+
+	  /* Loop only over the symbols whom been already checked.  */
+	  for (cur_sym_hashes = start_hashes; cur_sym_hashes < sym_hashes;
+	       cur_sym_hashes++)
+	    /* If the current symbol is identical to 'sym_hash', that means
+	       the symbol was already adjusted (or at least checked).  */
+	    if (*cur_sym_hashes == sym_hash)
+	      break;
+
+	  /* Don't adjust the symbol again.  */
+	  if (cur_sym_hashes < sym_hashes)
+	    continue;
+	}
+
+      if ((sym_hash->root.type == bfd_link_hash_defined
+	   || sym_hash->root.type == bfd_link_hash_defweak)
+	  && sym_hash->root.u.def.section == sec
+	  && sym_hash->root.u.def.value > addr
+	  && sym_hash->root.u.def.value < toaddr)
+	sym_hash->root.u.def.value -= count;
+    }
+
+  return TRUE;
+}
+
+/*
+ * Return true if LOC can be a target of a branch, jump or call.
+ */
+
+static int
+elf32_ft32_relax_is_branch_target (struct bfd_link_info *link_info,
+				   bfd * abfd, asection * sec,
+				   bfd_vma loc)
+{
+  Elf_Internal_Shdr *symtab_hdr;
+  Elf_Internal_Rela *irel, *irelend;
+  Elf_Internal_Sym *isym;
+  Elf_Internal_Sym *isymbuf = NULL;
+  bfd_vma symval;
+  struct bfd_section *isec;
+
+  struct elf_link_hash_entry **sym_hashes;
+  struct elf_link_hash_entry **end_hashes;
+  struct elf_link_hash_entry **start_hashes;
+  unsigned int symcount;
+
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+
+  /* Now we check for relocations pointing to ret.  */
+  for (isec = abfd->sections; isec; isec = isec->next)
+    {
+      irel = elf_section_data (isec)->relocs;
+      if (irel == NULL)
+	irel = _bfd_elf_link_read_relocs (abfd, isec, NULL, NULL, TRUE);
+
+      irelend = irel + isec->reloc_count;
+
+      for (; irel < irelend; irel++)
+	{
+	  /* Read this BFD's local symbols if we haven't done
+	     so already.  */
+	  if (isymbuf == NULL && symtab_hdr->sh_info != 0)
+	    {
+	      isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
+	      if (isymbuf == NULL)
+		isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr,
+						symtab_hdr->sh_info, 0,
+						NULL, NULL, NULL);
+	      if (isymbuf == NULL)
+		return 0;
+	    }
+
+	  /* Get the value of the symbol referred to by the reloc.  */
+	  if (ELF32_R_SYM (irel->r_info) < symtab_hdr->sh_info)
+	    {
+	      /* A local symbol.  */
+	      asection *sym_sec;
+
+	      isym = isymbuf + ELF32_R_SYM (irel->r_info);
+	      sym_sec = bfd_section_from_elf_index (abfd, isym->st_shndx);
+	      symval = isym->st_value;
+	      /* If the reloc is absolute, it will not have
+		 a symbol or section associated with it.  */
+	      if (sym_sec == sec)
+		{
+		  symval += sym_sec->output_section->vma
+                            + sym_sec->output_offset;
+
+		  if (debug_relax)
+		    printf ("0x%x: Address of anchor symbol: 0x%x "
+			    "Address of relocation target: 0x%x \n",
+			    (unsigned int) irel->r_offset,
+			    (unsigned int) symval,
+			    (unsigned int) (symval + irel->r_addend));
+		  if ((irel->r_addend) == loc)
+		    return 1;
+		}
+	    }
+	}
+    }
+  symcount = (symtab_hdr->sh_size / sizeof (Elf32_External_Sym)
+	       - symtab_hdr->sh_info);
+  sym_hashes = start_hashes = elf_sym_hashes (abfd);
+  end_hashes = sym_hashes + symcount;
+
+  for (; sym_hashes < end_hashes; sym_hashes++)
+    {
+      struct elf_link_hash_entry *sym_hash = *sym_hashes;
+
+      /* The '--wrap SYMBOL' option is causing a pain when the object file,
+	 containing the definition of __wrap_SYMBOL, includes a direct
+	 call to SYMBOL as well. Since both __wrap_SYMBOL and SYMBOL reference
+	 the same symbol (which is __wrap_SYMBOL), but still exist as two
+	 different symbols in 'sym_hashes', we don't want to adjust
+	 the global symbol __wrap_SYMBOL twice.
+	 This check is only relevant when symbols are being wrapped.  */
+      if (link_info->wrap_hash != NULL)
+	{
+	  struct elf_link_hash_entry **cur_sym_hashes;
+
+	  /* Loop only over the symbols whom been already checked.  */
+	  for (cur_sym_hashes = start_hashes; cur_sym_hashes < sym_hashes;
+	       cur_sym_hashes++)
+	    /* If the current symbol is identical to 'sym_hash', that means
+	       the symbol was already adjusted (or at least checked).  */
+	    if (*cur_sym_hashes == sym_hash)
+	      break;
+
+	  /* Don't adjust the symbol again.  */
+	  if (cur_sym_hashes < sym_hashes)
+	    continue;
+	}
+
+      if ((sym_hash->root.type == bfd_link_hash_defined
+	  || sym_hash->root.type == bfd_link_hash_defweak)
+	  && sym_hash->root.u.def.section == sec
+	  && sym_hash->root.u.def.value == loc)
+	    return 1;
+    }
+    return 0;
+}
+
+static bfd_boolean
+ft32_elf_relax_section
+    (bfd *                  abfd,
+     asection *             sec,
+     struct bfd_link_info * link_info,
+     bfd_boolean *          again)
+{
+  Elf_Internal_Rela * free_relocs = NULL;
+  Elf_Internal_Rela * internal_relocs;
+  Elf_Internal_Rela * irelend;
+  Elf_Internal_Rela * irel;
+  bfd_byte *          contents = NULL;
+  Elf_Internal_Shdr * symtab_hdr;
+  Elf_Internal_Sym *  isymbuf = NULL;
+
+  /* Assume nothing changes.  */
+  *again = FALSE;
+
+  /* We don't have to do anything for a relocatable link, if
+     this section does not have relocs, or if this is not a
+     code section.  */
+  if (bfd_link_relocatable (link_info)
+      || (sec->flags & SEC_RELOC) == 0
+      || sec->reloc_count == 0
+      || (sec->flags & SEC_CODE) == 0)
+    return TRUE;
+
+  /* Get the section contents.  */
+  if (elf_section_data (sec)->this_hdr.contents != NULL)
+    contents = elf_section_data (sec)->this_hdr.contents;
+  /* Go get them off disk.  */
+  else
+    {
+      if (! bfd_malloc_and_get_section (abfd, sec, &contents))
+	goto error_return;
+      elf_section_data (sec)->this_hdr.contents = contents;
+    }
+
+  symtab_hdr = &elf_tdata (abfd)->symtab_hdr;
+
+  /* Read this BFD's local symbols if we haven't done so already.  */
+  if (isymbuf == NULL && symtab_hdr->sh_info != 0)
+    {
+      isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
+      if (isymbuf == NULL)
+	isymbuf = bfd_elf_get_elf_syms (abfd, symtab_hdr,
+					symtab_hdr->sh_info, 0,
+					NULL, NULL, NULL);
+      if (isymbuf == NULL)
+	goto error_return;
+      symtab_hdr->contents = (unsigned char *) isymbuf;
+    }
+
+  internal_relocs = _bfd_elf_link_read_relocs (abfd, sec, NULL, NULL,
+					       link_info->keep_memory);
+  if (internal_relocs == NULL)
+    goto error_return;
+  if (! link_info->keep_memory)
+    free_relocs = internal_relocs;
+
+  /* Walk through them looking for relaxing opportunities.  */
+  irelend = internal_relocs + sec->reloc_count;
+
+  /* Test every adjacent pair of relocs. If both have shortcodes,
+   * fuse them and delete the relocs.
+   */
+  irel = internal_relocs;
+  while (irel < irelend - 1)
+    {
+      Elf_Internal_Rela * irel_next = irel + 1;
+      unsigned int sc0, sc1;
+      bfd_vma pc;
+
+      pc = irel->r_offset;
+
+      if (((pc + 4) == (irel_next->r_offset))
+	  && ft32_reloc_shortable (abfd, sec, isymbuf, contents, pc, irel,
+				   &sc0)
+	  && ft32_reloc_shortable (abfd, sec, isymbuf, contents, pc,
+				   irel_next, &sc1)
+	  && !elf32_ft32_relax_is_branch_target (link_info, abfd, sec,
+						 irel_next->r_offset))
+	{
+	  unsigned int code30 = (sc1 << 15) | sc0;
+	  unsigned int code27 = code30 >> 3;
+	  unsigned int code3 = code30 & 7;
+	  static const unsigned char pat3[] = {2, 3, 4, 5, 6, 9, 10, 14};
+	  unsigned int pattern = pat3[code3];
+	  unsigned int fused = (pattern << 27) | code27;
+
+	  /* Move second reloc to same place as first.  */
+	  irel_next->r_offset = irel->r_offset;
+
+	  /* Change both relocs to R_FT32_NONE.  */
+
+	  if (ELF32_R_TYPE (irel->r_info) == R_FT32_18)
+	    {
+	      irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
+                                           R_FT32_SC0);
+	    }
+	  else
+	    {
+	      irel->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
+                                           R_FT32_NONE);
+	    }
+
+	  if (ELF32_R_TYPE (irel_next->r_info) == R_FT32_18)
+	    {
+	      irel_next->r_info = ELF32_R_INFO (ELF32_R_SYM (irel_next->r_info),
+						R_FT32_SC1);
+	    }
+	  else
+	    {
+	      irel_next->r_info = ELF32_R_INFO (ELF32_R_SYM (irel->r_info),
+						R_FT32_NONE);
+	    }
+	  /* Replace the first insn with the fused version.  */
+	  bfd_put_32 (abfd, fused, contents + irel->r_offset);
+
+	  /* Delete the second insn.  */
+	  if (!elf32_ft32_relax_delete_bytes (link_info, abfd, sec,
+					       irel->r_offset + 4, 4))
+	    goto error_return;
+
+	  /* That will change things, so, we should relax again.
+	     Note that this is not required, and it may be slow.  */
+	  *again = TRUE;
+
+	  irel += 2;
+	}
+      else
+	{
+	  irel += 1;
+	}
+    }
+
+  if (isymbuf != NULL
+      && symtab_hdr->contents != (unsigned char *) isymbuf)
+    {
+      if (! link_info->keep_memory)
+	free (isymbuf);
+      else
+       /* Cache the symbols for elf_link_input_bfd.  */
+       symtab_hdr->contents = (unsigned char *) isymbuf;
+    }
+
+  if (contents != NULL
+      && elf_section_data (sec)->this_hdr.contents != contents)
+    {
+      if (! link_info->keep_memory)
+	free (contents);
+      else
+       /* Cache the section contents for elf_link_input_bfd.  */
+       elf_section_data (sec)->this_hdr.contents = contents;
+
+    }
+
+  if (internal_relocs != NULL
+      && elf_section_data (sec)->relocs != internal_relocs)
+    free (internal_relocs);
+
+  return TRUE;
+
+ error_return:
+  if (free_relocs != NULL)
+    free (free_relocs);
+
+  return TRUE;
+}
+
 #define ELF_ARCH		bfd_arch_ft32
 #define ELF_MACHINE_CODE	EM_FT32
 #define ELF_MAXPAGESIZE		0x1
@@ -387,4 +1253,6 @@ ft32_elf_relocate_section (bfd *output_bfd,
 #define bfd_elf32_bfd_reloc_type_lookup		ft32_reloc_type_lookup
 #define bfd_elf32_bfd_reloc_name_lookup		ft32_reloc_name_lookup
 
+#define bfd_elf32_bfd_relax_section		ft32_elf_relax_section
+
 #include "elf32-target.h"
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 0f8843e..bc45350 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -1216,7 +1216,11 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_FT32_20",
   "BFD_RELOC_FT32_17",
   "BFD_RELOC_FT32_18",
+  "BFD_RELOC_FT32_RELAX",
+  "BFD_RELOC_FT32_SC0",
+  "BFD_RELOC_FT32_SC1",
   "BFD_RELOC_FT32_15",
+  "BFD_RELOC_FT32_DIFF32",
 
   "BFD_RELOC_FRV_LABEL16",
   "BFD_RELOC_FRV_LABEL24",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index e4edfd3..7ee7844 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -2488,7 +2488,15 @@ ENUMX
 ENUMX
   BFD_RELOC_FT32_18
 ENUMX
+  BFD_RELOC_FT32_RELAX
+ENUMX
+  BFD_RELOC_FT32_SC0
+ENUMX
+  BFD_RELOC_FT32_SC1
+ENUMX
   BFD_RELOC_FT32_15
+ENUMX
+  BFD_RELOC_FT32_DIFF32
 ENUMDOC
   FT32 ELF relocations.
 COMMENT
diff --git a/gas/config/tc-ft32.c b/gas/config/tc-ft32.c
index 2878e14..fa60ea4 100644
--- a/gas/config/tc-ft32.c
+++ b/gas/config/tc-ft32.c
@@ -26,6 +26,9 @@
 
 extern const ft32_opc_info_t ft32_opc_info[128];
 
+/* See md_parse_option() for meanings of these options.  */
+static char norelax;			/* True if -norelax switch seen.  */
+
 const char comment_chars[]        = "#";
 const char line_separator_chars[] = ";";
 const char line_comment_chars[]   = "#";
@@ -58,6 +61,8 @@ md_begin (void)
     hash_insert (opcode_hash_control, opcode->name, (char *) opcode);
 
   bfd_set_arch_mach (stdoutput, TARGET_ARCH, 0);
+  if (!norelax)
+    linkrelax = 1;
 }
 
 /* Parse an expression and then restore the input line pointer.  */
@@ -204,6 +209,8 @@ md_assemble (char *str)
 
   expressionS arg;
 
+  int fixed = 0;
+
   /* Drop leading whitespace.  */
   while (*str == ' ')
     str++;
@@ -211,7 +218,10 @@ md_assemble (char *str)
   /* Find the op code end.  */
   op_start = str;
   for (op_end = str;
-       *op_end && !is_end_of_line[*op_end & 0xff] && *op_end != ' ' && *op_end != '.';
+       *op_end
+       && !is_end_of_line[*op_end & 0xff]
+       && *op_end != ' '
+       && *op_end != '.';
        op_end++)
     nlen++;
 
@@ -301,6 +311,7 @@ md_assemble (char *str)
               {
                 b |= 0x400 << FT32_FLD_RIMM_BIT;
                 op_end = parse_exp_save_ilp (op_end, &arg);
+                fixed = 1;
                 fix_new_exp (frag_now,
                              (output - frag_now->fr_literal),
                              2,
@@ -314,6 +325,7 @@ md_assemble (char *str)
             break;
           case  FT32_FLD_K20:
             op_end = parse_exp_save_ilp (op_end, &arg);
+            fixed = 1;
             fix_new_exp (frag_now,
                          (output - frag_now->fr_literal),
                          3,
@@ -323,6 +335,7 @@ md_assemble (char *str)
             break;
           case  FT32_FLD_PA:
             op_end = parse_exp_save_ilp (op_end, &arg);
+            fixed = 1;
             fix_new_exp (frag_now,
                          (output - frag_now->fr_literal),
                          3,
@@ -332,6 +345,7 @@ md_assemble (char *str)
             break;
           case  FT32_FLD_AA:
             op_end = parse_exp_save_ilp (op_end, &arg);
+            fixed = 1;
             fix_new_exp (frag_now,
                          (output - frag_now->fr_literal),
                          3,
@@ -341,6 +355,7 @@ md_assemble (char *str)
             break;
           case  FT32_FLD_K16:
             op_end = parse_exp_save_ilp (op_end, &arg);
+            fixed = 1;
             fix_new_exp (frag_now,
                          (output - frag_now->fr_literal),
                          2,
@@ -352,6 +367,7 @@ md_assemble (char *str)
             op_end = parse_exp_save_ilp (op_end, &arg);
             if (arg.X_add_number & 0x80)
               arg.X_add_number ^= 0x7f00;
+            fixed = 1;
             fix_new_exp (frag_now,
                          (output - frag_now->fr_literal),
                          2,
@@ -390,6 +406,23 @@ md_assemble (char *str)
   if (*op_end != 0)
     as_warn (_("extra stuff on line ignored"));
 
+  unsigned int sc;
+  int can_sc = ft32_shortcode(b, &sc);
+
+  if (!fixed && can_sc)
+    {
+      arg.X_op = O_constant;
+      arg.X_add_number = 0;
+      arg.X_add_symbol = NULL;
+      arg.X_op_symbol = NULL;
+      fix_new_exp (frag_now,
+                   (output - frag_now->fr_literal),
+                   2,
+                   &arg,
+                   0,
+                   BFD_RELOC_FT32_RELAX);
+    }
+
   output[idx++] = 0xff & (b >> 0);
   output[idx++] = 0xff & (b >> 8);
   output[idx++] = 0xff & (b >> 16);
@@ -454,6 +487,9 @@ const char *md_shortopts = "";
 
 struct option md_longopts[] =
 {
+#define OPTION_NORELAX (OPTION_MD_BASE)
+  {"norelax", no_argument, NULL, OPTION_NORELAX},
+  {"no-relax", no_argument, NULL, OPTION_NORELAX},
   {NULL, no_argument, NULL, 0}
 };
 size_t md_longopts_size = sizeof (md_longopts);
@@ -463,12 +499,26 @@ size_t md_longopts_size = sizeof (md_longopts);
 int
 md_parse_option (int c ATTRIBUTE_UNUSED, const char *arg ATTRIBUTE_UNUSED)
 {
-  return 0;
+  switch (c)
+    {
+    case OPTION_NORELAX:
+      norelax = 1;
+      break;
+
+    default:
+      return 0;
+    }
+
+  return 1;
 }
 
 void
 md_show_usage (FILE *stream ATTRIBUTE_UNUSED)
 {
+  fprintf (stream, _("FT32 options:\n"));
+  fprintf (stream, _("\n\
+-no-relax		don't relax relocations\n\
+			\n"));
 }
 
 /* Convert from target byte order to host byte order.  */
@@ -497,8 +547,42 @@ md_apply_fix (fixS *fixP ATTRIBUTE_UNUSED,
   long val = *valP;
   long newval;
 
+  if (linkrelax && fixP->fx_subsy)
+    {
+      /* For a subtraction relocation expression, generate one
+         of the DIFF relocs, with the value being the difference.
+         Note that a sym1 - sym2 expression is adjusted into a
+         section_start_sym + sym4_offset_from_section_start - sym1
+         expression. fixP->fx_addsy holds the section start symbol,
+         fixP->fx_offset holds sym2's offset, and fixP->fx_subsy
+         holds sym1. Calculate the current difference and write value,
+         but leave fx_offset as is - during relaxation,
+         fx_offset - value gives sym1's value.  */
+
+       switch (fixP->fx_r_type)
+         {
+           case BFD_RELOC_32:
+             fixP->fx_r_type = BFD_RELOC_FT32_DIFF32;
+             break;
+           default:
+             as_bad_where (fixP->fx_file, fixP->fx_line,
+                           _("expression too complex"));
+             break;
+         }
+
+      val = S_GET_VALUE (fixP->fx_addsy) +
+          fixP->fx_offset - S_GET_VALUE (fixP->fx_subsy);
+      *valP = val;
+
+      fixP->fx_subsy = NULL;
+  }
+  /* We don't actually support subtracting a symbol.  */
+  if (fixP->fx_subsy != (symbolS *) NULL)
+    as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex"));
+
   switch (fixP->fx_r_type)
     {
+    case BFD_RELOC_FT32_DIFF32:
     case BFD_RELOC_32:
       buf[3] = val >> 24;
       buf[2] = val >> 16;
@@ -555,13 +639,15 @@ md_apply_fix (fixS *fixP ATTRIBUTE_UNUSED,
       md_number_to_chars (buf, newval, 4);
       break;
 
+    case BFD_RELOC_FT32_RELAX:
+      break;
+
     default:
       abort ();
     }
 
   if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0)
     fixP->fx_done = 1;
-  // printf("fx_addsy=%p fixP->fx_pcrel=%d fx_done=%d\n", fixP->fx_addsy, fixP->fx_pcrel, fixP->fx_done);
 }
 
 void
@@ -587,11 +673,14 @@ tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixP)
     case BFD_RELOC_FT32_15:
     case BFD_RELOC_FT32_17:
     case BFD_RELOC_FT32_18:
+    case BFD_RELOC_FT32_RELAX:
+    case BFD_RELOC_FT32_DIFF32:
       code = fixP->fx_r_type;
       break;
     default:
       as_bad_where (fixP->fx_file, fixP->fx_line,
-		    _("Semantics error.  This type of operand can not be relocated, it must be an assembly-time constant"));
+		    _("Semantics error.  This type of operand can not be "
+                      "relocated, it must be an assembly-time constant"));
       return 0;
     }
 
@@ -617,3 +706,77 @@ tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixP)
 
   return relP;
 }
+
+/* TC_FORCE_RELOCATION hook */
+
+static bfd_boolean
+relaxable_section (asection *sec)
+{
+  return ((sec->flags & SEC_DEBUGGING) == 0
+          && (sec->flags & SEC_CODE) != 0
+          && (sec->flags & SEC_ALLOC) != 0);
+}
+
+/* Does whatever the xtensa port does.  */
+int
+ft32_validate_fix_sub (fixS *fix)
+{
+  segT add_symbol_segment, sub_symbol_segment;
+
+  /* The difference of two symbols should be resolved by the assembler when
+     linkrelax is not set.  If the linker may relax the section containing
+     the symbols, then an Xtensa DIFF relocation must be generated so that
+     the linker knows to adjust the difference value.  */
+  if (!linkrelax || fix->fx_addsy == NULL)
+    return 0;
+
+  /* Make sure both symbols are in the same segment, and that segment is
+     "normal" and relaxable.  If the segment is not "normal", then the
+     fix is not valid.  If the segment is not "relaxable", then the fix
+     should have been handled earlier.  */
+  add_symbol_segment = S_GET_SEGMENT (fix->fx_addsy);
+  if (! SEG_NORMAL (add_symbol_segment) ||
+      ! relaxable_section (add_symbol_segment))
+    return 0;
+
+  sub_symbol_segment = S_GET_SEGMENT (fix->fx_subsy);
+  return (sub_symbol_segment == add_symbol_segment);
+}
+
+/* TC_FORCE_RELOCATION hook */
+
+/* If linkrelax is turned on, and the symbol to relocate
+   against is in a relaxable segment, don't compute the value -
+   generate a relocation instead.  */
+int
+ft32_force_relocation (fixS *fix)
+{
+  if (linkrelax && fix->fx_addsy
+      && relaxable_section (S_GET_SEGMENT (fix->fx_addsy))) {
+    return 1;
+  }
+
+  return generic_force_reloc (fix);
+}
+
+bfd_boolean
+ft32_allow_local_subtract (expressionS * left,
+			   expressionS * right,
+			   segT section)
+{
+  /* If we are not in relaxation mode, subtraction is OK.  */
+  if (!linkrelax)
+    return TRUE;
+
+  /* If the symbols are not in a code section then they are OK.  */
+  if ((section->flags & SEC_CODE) == 0)
+    return TRUE;
+
+  if (left->X_add_symbol == right->X_add_symbol)
+    return TRUE;
+
+  /* We have to assume that there may be instructions between the
+     two symbols and that relaxation may increase the distance between
+     them.  */
+  return FALSE;
+}
diff --git a/gas/config/tc-ft32.h b/gas/config/tc-ft32.h
index cc80c81..b34a886 100644
--- a/gas/config/tc-ft32.h
+++ b/gas/config/tc-ft32.h
@@ -35,14 +35,6 @@
 #define md_estimate_size_before_relax(A, B) (as_fatal (_("estimate size\n")), 0)
 #define md_convert_frag(B, S, F)            (as_fatal (_("convert_frag\n")))
 
-/* If you define this macro, it should return the offset between the
-   address of a PC relative fixup and the position from which the PC
-   relative adjustment should be made.  On many processors, the base
-   of a PC relative instruction is the next instruction, so this
-   macro would return the length of an instruction.  */
-// #define MD_PCREL_FROM_SECTION(FIX, SEC) md_pcrel_from (FIX)
-// extern long md_pcrel_from (struct fix *);
-
 /* PC relative operands are relative to the start of the opcode, and
    the operand is always one byte into the opcode.  */
 #define md_pcrel_from(FIX)						\
@@ -50,4 +42,27 @@
 
 #define md_section_align(SEGMENT, SIZE)     (SIZE)
 
+/* If this macro returns non-zero, it guarantees that a relocation will be emitted
+   even when the value can be resolved locally. Do that if linkrelax is turned on */
+#define TC_FORCE_RELOCATION(fix)	ft32_force_relocation (fix)
+#define TC_FORCE_RELOCATION_SUB_SAME(fix, seg) \
+  (! SEG_NORMAL (seg) || ft32_force_relocation (fix))
+extern int ft32_force_relocation (struct fix *);
+
+#define TC_LINKRELAX_FIXUP(seg) \
+  ((seg->flags & SEC_CODE) || (seg->flags & SEC_DEBUGGING))
+
+/* This macro is evaluated for any fixup with a fx_subsy that
+   fixup_segment cannot reduce to a number.  If the macro returns
+   false an error will be reported. */
+#define TC_VALIDATE_FIX_SUB(fix, seg)   ft32_validate_fix_sub (fix)
+extern int ft32_validate_fix_sub (struct fix *);
+
+/* The difference between same-section symbols may be affected by linker
+   relaxation, so do not resolve such expressions in the assembler.  */
+#define md_allow_local_subtract(l,r,s) ft32_allow_local_subtract (l, r, s)
+extern bfd_boolean ft32_allow_local_subtract (expressionS *,
+                                              expressionS *,
+                                              segT);
+
 #define md_operand(x)
diff --git a/include/elf/ft32.h b/include/elf/ft32.h
index 4713bff..16fb08e 100644
--- a/include/elf/ft32.h
+++ b/include/elf/ft32.h
@@ -32,7 +32,11 @@ START_RELOC_NUMBERS (elf_ft32_reloc_type)
   RELOC_NUMBER (R_FT32_20, 5)
   RELOC_NUMBER (R_FT32_17, 6)
   RELOC_NUMBER (R_FT32_18, 7)
+  RELOC_NUMBER (R_FT32_RELAX, 8)
+  RELOC_NUMBER (R_FT32_SC0, 9)
+  RELOC_NUMBER (R_FT32_SC1, 10)
   RELOC_NUMBER (R_FT32_15, 11)
+  RELOC_NUMBER (R_FT32_DIFF32, 12)
 END_RELOC_NUMBERS (R_FT32_max)
 
 #endif /* _ELF_FT32_H */


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