From 1e50d24d553250622687bbc21027e8bfdb31870f Mon Sep 17 00:00:00 2001
From: Richard Sandiford <rdsandiford@googlemail.com>
Date: Thu, 16 Dec 2004 16:56:04 +0000
Subject: [PATCH] include/elf/ 	* v850.h (R_V850_LO16_SPLIT_OFFSET): New
 reloc.

bfd/
	* reloc.c (BFD_RELOC_V850_LO16_SPLIT_OFFSET): New bfd_reloc_code_type.
	* elf32-v850.c (v850_elf_howto_table): Add entry for
	R_V850_LO16_SPLIT_OFFSET.
	(v850_elf_reloc_map): Map it to BFD_RELOC_V850_LO16_SPLIT_OFFSET.
	(v850_elf_perform_lo16_relocation): New function, extracted from...
	(v850_elf_perform_relocation): ...here.  Use it to handle
	R_V850_LO16_SPLIT_OFFSET.
	(v850_elf_check_relocs, v850_elf_final_link_relocate): Handle
	R_V850_LO16_SPLIT_OFFSET.
	* libbfd.h, bfd-in2.h: Regenerate.

gas/
	* config/tc-v850.c (handle_lo16): New function.
	(v850_reloc_prefix): Use it to check lo().
	(md_assemble, md_apply_fix3): Handle BFD_RELOC_V850_LO16_SPLIT_OFFSET.

gas/testsuite/
	* gas/v850/split-lo16.{s,d}: New test.
	* gas/v850/v850.exp: Run it.

ld/testsuite/
	* ld-v850: New directory.
---
 bfd/ChangeLog                       |  13 +
 bfd/bfd-in2.h                       |   4 +
 bfd/elf32-v850.c                    | 392 +++++++++++++++-------------
 bfd/libbfd.h                        |   1 +
 bfd/reloc.c                         |   5 +
 gas/ChangeLog                       |   6 +
 gas/config/tc-v850.c                |  29 +-
 gas/testsuite/ChangeLog             |   5 +
 gas/testsuite/gas/v850/basic.exp    |   1 +
 gas/testsuite/gas/v850/split-lo16.d |  16 ++
 gas/testsuite/gas/v850/split-lo16.s |   7 +
 include/elf/ChangeLog               |   4 +
 include/elf/v850.h                  |   1 +
 ld/testsuite/ChangeLog              |   4 +
 ld/testsuite/ld-v850/split-lo16.d   |  26 ++
 ld/testsuite/ld-v850/split-lo16.ld  |  11 +
 ld/testsuite/ld-v850/split-lo16.s   |  20 ++
 ld/testsuite/ld-v850/v850.exp       |   3 +
 18 files changed, 369 insertions(+), 179 deletions(-)
 create mode 100644 gas/testsuite/gas/v850/split-lo16.d
 create mode 100644 gas/testsuite/gas/v850/split-lo16.s
 create mode 100644 ld/testsuite/ld-v850/split-lo16.d
 create mode 100644 ld/testsuite/ld-v850/split-lo16.ld
 create mode 100644 ld/testsuite/ld-v850/split-lo16.s
 create mode 100644 ld/testsuite/ld-v850/v850.exp

diff --git a/bfd/ChangeLog b/bfd/ChangeLog
index 9f1c90f29a1..70af75209a1 100644
--- a/bfd/ChangeLog
+++ b/bfd/ChangeLog
@@ -1,3 +1,16 @@
+2004-12-16  Richard Sandiford  <rsandifo@redhat.com>
+
+	* reloc.c (BFD_RELOC_V850_LO16_SPLIT_OFFSET): New bfd_reloc_code_type.
+	* elf32-v850.c (v850_elf_howto_table): Add entry for
+	R_V850_LO16_SPLIT_OFFSET.
+	(v850_elf_reloc_map): Map it to BFD_RELOC_V850_LO16_SPLIT_OFFSET.
+	(v850_elf_perform_lo16_relocation): New function, extracted from...
+	(v850_elf_perform_relocation): ...here.  Use it to handle
+	R_V850_LO16_SPLIT_OFFSET.
+	(v850_elf_check_relocs, v850_elf_final_link_relocate): Handle
+	R_V850_LO16_SPLIT_OFFSET.
+	* libbfd.h, bfd-in2.h: Regenerate.
+
 2004-12-14  P.J. Darcy  <darcypj@us.ibm.com>
 
 	* config.bfd: Add s390x-ibm-tpf support.
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index f60382323b6..8f7a3d42519 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -2967,6 +2967,10 @@ bits placed non-contiguously in the instruction.  */
 /* Used to maintain alignment whilst relaxing.  */
   BFD_RELOC_V850_ALIGN,
 
+/* This is a variation of BFD_RELOC_LO16 that can be used in v850e ld.bu
+instructions.  */
+  BFD_RELOC_V850_LO16_SPLIT_OFFSET,
+
 /* This is a 32bit pcrel reloc for the mn10300, offset by two bytes in the
 instruction.  */
   BFD_RELOC_MN10300_32_PCREL,
diff --git a/bfd/elf32-v850.c b/bfd/elf32-v850.c
index 09eb1ab65a6..d960235298a 100644
--- a/bfd/elf32-v850.c
+++ b/bfd/elf32-v850.c
@@ -533,6 +533,21 @@ static reloc_howto_type v850_elf_howto_table[] =
 	 0xffffffff,			/* src_mask */
 	 0xffffffff,			/* dst_mask */
 	 FALSE),			/* pcrel_offset */
+
+  /* An ld.bu version of R_V850_LO16.  */
+  HOWTO (R_V850_LO16_SPLIT_OFFSET,	/* type */
+	 0,				/* rightshift */
+	 2,				/* size (0 = byte, 1 = short, 2 = long) */
+	 16,				/* bitsize */
+	 FALSE,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_dont,	/* complain_on_overflow */
+	 v850_elf_reloc,		/* special_function */
+	 "R_V850_LO16_SPLIT_OFFSET",	/* name */
+	 FALSE,				/* partial_inplace */
+	 0xfffe0020,			/* src_mask */
+	 0xfffe0020,			/* dst_mask */
+	 FALSE),			/* pcrel_offset */
 };
 
 /* Map BFD reloc types to V850 ELF reloc types.  */
@@ -567,6 +582,7 @@ static const struct v850_elf_reloc_map v850_elf_reloc_map[] =
   { BFD_RELOC_V850_TDA_16_16_OFFSET,       R_V850_TDA_16_16_OFFSET       },
   { BFD_RELOC_V850_TDA_4_5_OFFSET,         R_V850_TDA_4_5_OFFSET         },
   { BFD_RELOC_V850_TDA_4_4_OFFSET,         R_V850_TDA_4_4_OFFSET         },
+  { BFD_RELOC_V850_LO16_SPLIT_OFFSET,      R_V850_LO16_SPLIT_OFFSET      },
   { BFD_RELOC_V850_SDA_16_16_SPLIT_OFFSET, R_V850_SDA_16_16_SPLIT_OFFSET },
   { BFD_RELOC_V850_ZDA_16_16_SPLIT_OFFSET, R_V850_ZDA_16_16_SPLIT_OFFSET },
   { BFD_RELOC_V850_CALLT_6_7_OFFSET,       R_V850_CALLT_6_7_OFFSET       },
@@ -687,6 +703,7 @@ v850_elf_check_relocs (abfd, info, sec, relocs)
 	case R_V850_HI16_S:
 	case R_V850_HI16:
 	case R_V850_LO16:
+	case R_V850_LO16_SPLIT_OFFSET:
 	case R_V850_ABS32:
 	case R_V850_REL32:
 	case R_V850_16:
@@ -902,6 +919,187 @@ find_remembered_hi16s_reloc (addend, already_found)
   return addr;
 }
 
+/* Calculate the final operand value for a R_V850_LO16 or
+   R_V850_LO16_SPLIT_OFFSET.  *INSN is the current operand value and
+   ADDEND is the sum of the relocation symbol and offset.  Store the
+   operand value in *INSN and return true on success.
+
+   The assembler has already done some of this: If the value stored in
+   the instruction has its 15th bit set, (counting from zero) then the
+   assembler will have added 1 to the value stored in the associated
+   HI16S reloc.  So for example, these relocations:
+
+       movhi hi( fred ), r0, r1
+       movea lo( fred ), r1, r1
+
+   will store 0 in the value fields for the MOVHI and MOVEA instructions
+   and addend will be the address of fred, but for these instructions:
+
+       movhi hi( fred + 0x123456), r0, r1
+       movea lo( fred + 0x123456), r1, r1
+
+   the value stored in the MOVHI instruction will be 0x12 and the value
+   stored in the MOVEA instruction will be 0x3456.  If however the
+   instructions were:
+
+       movhi hi( fred + 0x10ffff), r0, r1
+       movea lo( fred + 0x10ffff), r1, r1
+
+   then the value stored in the MOVHI instruction would be 0x11 (not
+   0x10) and the value stored in the MOVEA instruction would be 0xffff.
+   Thus (assuming for the moment that the addend is 0), at run time the
+   MOVHI instruction loads 0x110000 into r1, then the MOVEA instruction
+   adds 0xffffffff (sign extension!) producing 0x10ffff.  Similarly if
+   the instructions were:
+
+       movhi hi( fred - 1), r0, r1
+       movea lo( fred - 1), r1, r1
+
+   then 0 is stored in the MOVHI instruction and -1 is stored in the
+   MOVEA instruction.
+
+   Overflow can occur if the addition of the value stored in the
+   instruction plus the addend sets the 15th bit when before it was clear.
+   This is because the 15th bit will be sign extended into the high part,
+   thus reducing its value by one, but since the 15th bit was originally
+   clear, the assembler will not have added 1 to the previous HI16S reloc
+   to compensate for this effect.  For example:
+
+      movhi hi( fred + 0x123456), r0, r1
+      movea lo( fred + 0x123456), r1, r1
+
+   The value stored in HI16S reloc is 0x12, the value stored in the LO16
+   reloc is 0x3456.  If we assume that the address of fred is 0x00007000
+   then the relocations become:
+
+     HI16S: 0x0012 + (0x00007000 >> 16)    = 0x12
+     LO16:  0x3456 + (0x00007000 & 0xffff) = 0xa456
+
+   but when the instructions are executed, the MOVEA instruction's value
+   is signed extended, so the sum becomes:
+
+	0x00120000
+      + 0xffffa456
+      ------------
+	0x0011a456    but 'fred + 0x123456' = 0x0012a456
+
+   Note that if the 15th bit was set in the value stored in the LO16
+   reloc, then we do not have to do anything:
+
+      movhi hi( fred + 0x10ffff), r0, r1
+      movea lo( fred + 0x10ffff), r1, r1
+
+      HI16S:  0x0011 + (0x00007000 >> 16)    = 0x11
+      LO16:   0xffff + (0x00007000 & 0xffff) = 0x6fff
+
+	0x00110000
+      + 0x00006fff
+      ------------
+	0x00116fff  = fred + 0x10ffff = 0x7000 + 0x10ffff
+
+   Overflow can also occur if the computation carries into the 16th bit
+   and it also results in the 15th bit having the same value as the 15th
+   bit of the original value.   What happens is that the HI16S reloc
+   will have already examined the 15th bit of the original value and
+   added 1 to the high part if the bit is set.  This compensates for the
+   sign extension of 15th bit of the result of the computation.  But now
+   there is a carry into the 16th bit, and this has not been allowed for.
+
+   So, for example if fred is at address 0xf000:
+
+     movhi hi( fred + 0xffff), r0, r1    [bit 15 of the offset is set]
+     movea lo( fred + 0xffff), r1, r1
+
+     HI16S: 0x0001 + (0x0000f000 >> 16)    = 0x0001
+     LO16:  0xffff + (0x0000f000 & 0xffff) = 0xefff   (carry into bit 16 is lost)
+
+       0x00010000
+     + 0xffffefff
+     ------------
+       0x0000efff   but 'fred + 0xffff' = 0x0001efff
+
+   Similarly, if the 15th bit remains clear, but overflow occurs into
+   the 16th bit then (assuming the address of fred is 0xf000):
+
+     movhi hi( fred + 0x7000), r0, r1    [bit 15 of the offset is clear]
+     movea lo( fred + 0x7000), r1, r1
+
+     HI16S: 0x0000 + (0x0000f000 >> 16)    = 0x0000
+     LO16:  0x7000 + (0x0000f000 & 0xffff) = 0x6fff  (carry into bit 16 is lost)
+
+       0x00000000
+     + 0x00006fff
+     ------------
+       0x00006fff   but 'fred + 0x7000' = 0x00016fff
+
+   Note - there is no need to change anything if a carry occurs, and the
+   15th bit changes its value from being set to being clear, as the HI16S
+   reloc will have already added in 1 to the high part for us:
+
+     movhi hi( fred + 0xffff), r0, r1     [bit 15 of the offset is set]
+     movea lo( fred + 0xffff), r1, r1
+
+     HI16S: 0x0001 + (0x00007000 >> 16)
+     LO16:  0xffff + (0x00007000 & 0xffff) = 0x6fff  (carry into bit 16 is lost)
+
+       0x00010000
+     + 0x00006fff   (bit 15 not set, so the top half is zero)
+     ------------
+       0x00016fff   which is right (assuming that fred is at 0x7000)
+
+   but if the 15th bit goes from being clear to being set, then we must
+   once again handle overflow:
+
+     movhi hi( fred + 0x7000), r0, r1     [bit 15 of the offset is clear]
+     movea lo( fred + 0x7000), r1, r1
+
+     HI16S: 0x0000 + (0x0000ffff >> 16)
+     LO16:  0x7000 + (0x0000ffff & 0xffff) = 0x6fff  (carry into bit 16)
+
+       0x00000000
+     + 0x00006fff   (bit 15 not set, so the top half is zero)
+     ------------
+       0x00006fff   which is wrong (assuming that fred is at 0xffff).  */
+
+static bfd_boolean
+v850_elf_perform_lo16_relocation (bfd *abfd, unsigned long *insn,
+				  unsigned long addend)
+{
+#define BIT15_SET(x) ((x) & 0x8000)
+#define OVERFLOWS(a,i) ((((a) & 0xffff) + (i)) > 0xffff)
+  if ((BIT15_SET (*insn + addend) && ! BIT15_SET (addend))
+      || (OVERFLOWS (addend, *insn)
+	  && ((! BIT15_SET (*insn)) || (BIT15_SET (addend)))))
+    {
+      bfd_boolean already_updated;
+      bfd_byte *hi16s_address = find_remembered_hi16s_reloc
+	(addend, & already_updated);
+
+      /* Amend the matching HI16_S relocation.  */
+      if (hi16s_address != NULL)
+	{
+	  if (! already_updated)
+	    {
+	      unsigned long hi_insn = bfd_get_16 (abfd, hi16s_address);
+	      hi_insn += 1;
+	      bfd_put_16 (abfd, hi_insn, hi16s_address);
+	    }
+	}
+      else
+	{
+	  fprintf (stderr, _("FAILED to find previous HI16 reloc\n"));
+	  return FALSE;
+	}
+    }
+#undef OVERFLOWS
+#undef BIT15_SET
+
+  /* Do not complain if value has top bit set, as this has been
+     anticipated.  */
+  *insn = (*insn + addend) & 0xffff;
+  return TRUE;
+}
+
 /* FIXME:  The code here probably ought to be removed and the code in reloc.c
    allowed to do its stuff instead.  At least for most of the relocs, anyway.  */
 
@@ -913,6 +1111,7 @@ v850_elf_perform_relocation (abfd, r_type, addend, address)
      bfd_byte *address;
 {
   unsigned long insn;
+  unsigned long result;
   bfd_signed_vma saddend = (bfd_signed_vma) addend;
 
   switch (r_type)
@@ -972,183 +1171,10 @@ v850_elf_perform_relocation (abfd, r_type, addend, address)
       break;
 
     case R_V850_LO16:
-      /* Calculate the sum of the value stored in the instruction and the
-	 addend and check for overflow from the low 16 bits into the high
-	 16 bits.  The assembler has already done some of this:  If the
-	 value stored in the instruction has its 15th bit set, (counting
-	 from zero) then the assembler will have added 1 to the value
-	 stored in the associated HI16S reloc.  So for example, these
-	 relocations:
-
-	     movhi hi( fred ), r0, r1
-	     movea lo( fred ), r1, r1
-
-	 will store 0 in the value fields for the MOVHI and MOVEA instructions
-	 and addend will be the address of fred, but for these instructions:
-
-	     movhi hi( fred + 0x123456), r0, r1
-	     movea lo( fred + 0x123456), r1, r1
-
-	 the value stored in the MOVHI instruction will be 0x12 and the value
-	 stored in the MOVEA instruction will be 0x3456.  If however the
-	 instructions were:
-
-	     movhi hi( fred + 0x10ffff), r0, r1
-	     movea lo( fred + 0x10ffff), r1, r1
-
-	 then the value stored in the MOVHI instruction would be 0x11 (not
-	 0x10) and the value stored in the MOVEA instruction would be 0xffff.
-	 Thus (assuming for the moment that the addend is 0), at run time the
-	 MOVHI instruction loads 0x110000 into r1, then the MOVEA instruction
-	 adds 0xffffffff (sign extension!) producing 0x10ffff.  Similarly if
-	 the instructions were:
-
-	     movhi hi( fred - 1), r0, r1
-	     movea lo( fred - 1), r1, r1
-
-	 then 0 is stored in the MOVHI instruction and -1 is stored in the
-	 MOVEA instruction.
-
-	 Overflow can occur if the addition of the value stored in the
-	 instruction plus the addend sets the 15th bit when before it was clear.
-	 This is because the 15th bit will be sign extended into the high part,
-	 thus reducing its value by one, but since the 15th bit was originally
-	 clear, the assembler will not have added 1 to the previous HI16S reloc
-	 to compensate for this effect.  For example:
-
-	    movhi hi( fred + 0x123456), r0, r1
-	    movea lo( fred + 0x123456), r1, r1
-
-	 The value stored in HI16S reloc is 0x12, the value stored in the LO16
-	 reloc is 0x3456.  If we assume that the address of fred is 0x00007000
-	 then the relocations become:
-
-	   HI16S: 0x0012 + (0x00007000 >> 16)    = 0x12
-	   LO16:  0x3456 + (0x00007000 & 0xffff) = 0xa456
-
-	 but when the instructions are executed, the MOVEA instruction's value
-	 is signed extended, so the sum becomes:
-
-	      0x00120000
-	    + 0xffffa456
-	    ------------
-	      0x0011a456    but 'fred + 0x123456' = 0x0012a456
-
-	 Note that if the 15th bit was set in the value stored in the LO16
-	 reloc, then we do not have to do anything:
-
-	    movhi hi( fred + 0x10ffff), r0, r1
-	    movea lo( fred + 0x10ffff), r1, r1
-
-	    HI16S:  0x0011 + (0x00007000 >> 16)    = 0x11
-	    LO16:   0xffff + (0x00007000 & 0xffff) = 0x6fff
-
-	      0x00110000
-	    + 0x00006fff
-	    ------------
-	      0x00116fff  = fred + 0x10ffff = 0x7000 + 0x10ffff
-
-	 Overflow can also occur if the computation carries into the 16th bit
-	 and it also results in the 15th bit having the same value as the 15th
-	 bit of the original value.   What happens is that the HI16S reloc
-	 will have already examined the 15th bit of the original value and
-	 added 1 to the high part if the bit is set.  This compensates for the
-	 sign extension of 15th bit of the result of the computation.  But now
-	 there is a carry into the 16th bit, and this has not been allowed for.
-
-	 So, for example if fred is at address 0xf000:
-
-	   movhi hi( fred + 0xffff), r0, r1    [bit 15 of the offset is set]
-	   movea lo( fred + 0xffff), r1, r1
-
-	   HI16S: 0x0001 + (0x0000f000 >> 16)    = 0x0001
-	   LO16:  0xffff + (0x0000f000 & 0xffff) = 0xefff   (carry into bit 16 is lost)
-
-	     0x00010000
-	   + 0xffffefff
-	   ------------
-	     0x0000efff   but 'fred + 0xffff' = 0x0001efff
-
-	 Similarly, if the 15th bit remains clear, but overflow occurs into
-	 the 16th bit then (assuming the address of fred is 0xf000):
-
-	   movhi hi( fred + 0x7000), r0, r1    [bit 15 of the offset is clear]
-	   movea lo( fred + 0x7000), r1, r1
-
-	   HI16S: 0x0000 + (0x0000f000 >> 16)    = 0x0000
-	   LO16:  0x7000 + (0x0000f000 & 0xffff) = 0x6fff  (carry into bit 16 is lost)
-
-	     0x00000000
-	   + 0x00006fff
-	   ------------
-	     0x00006fff   but 'fred + 0x7000' = 0x00016fff
-
-	 Note - there is no need to change anything if a carry occurs, and the
-	 15th bit changes its value from being set to being clear, as the HI16S
-	 reloc will have already added in 1 to the high part for us:
-
-	   movhi hi( fred + 0xffff), r0, r1     [bit 15 of the offset is set]
-	   movea lo( fred + 0xffff), r1, r1
-
-	   HI16S: 0x0001 + (0x00007000 >> 16)
-	   LO16:  0xffff + (0x00007000 & 0xffff) = 0x6fff  (carry into bit 16 is lost)
-
-	     0x00010000
-	   + 0x00006fff   (bit 15 not set, so the top half is zero)
-	   ------------
-	     0x00016fff   which is right (assuming that fred is at 0x7000)
-
-	 but if the 15th bit goes from being clear to being set, then we must
-	 once again handle overflow:
-
-	   movhi hi( fred + 0x7000), r0, r1     [bit 15 of the offset is clear]
-	   movea lo( fred + 0x7000), r1, r1
-
-	   HI16S: 0x0000 + (0x0000ffff >> 16)
-	   LO16:  0x7000 + (0x0000ffff & 0xffff) = 0x6fff  (carry into bit 16)
-
-	     0x00000000
-	   + 0x00006fff   (bit 15 not set, so the top half is zero)
-	   ------------
-	     0x00006fff   which is wrong (assuming that fred is at 0xffff).  */
-      {
-	long result;
-
-	insn   = bfd_get_16 (abfd, address);
-	result = insn + addend;
-
-#define BIT15_SET(x) ((x) & 0x8000)
-#define OVERFLOWS(a,i) ((((a) & 0xffff) + (i)) > 0xffff)
-
-	if ((BIT15_SET (result) && ! BIT15_SET (addend))
-	    || (OVERFLOWS (addend, insn)
-		&& ((! BIT15_SET (insn)) || (BIT15_SET (addend)))))
-	  {
-	    bfd_boolean already_updated;
-	    bfd_byte *hi16s_address = find_remembered_hi16s_reloc
-	      (addend, & already_updated);
-
-	    /* Amend the matching HI16_S relocation.  */
-	    if (hi16s_address != NULL)
-	      {
-		if (! already_updated)
-		  {
-		    insn = bfd_get_16 (abfd, hi16s_address);
-		    insn += 1;
-		    bfd_put_16 (abfd, (bfd_vma) insn, hi16s_address);
-		  }
-	      }
-	    else
-	      {
-		fprintf (stderr, _("FAILED to find previous HI16 reloc\n"));
-		return bfd_reloc_overflow;
-	      }
-	  }
-
-	/* Do not complain if value has top bit set, as this has been anticipated.  */
-	insn = result & 0xffff;
-	break;
-      }
+      insn = bfd_get_16 (abfd, address);
+      if (! v850_elf_perform_lo16_relocation (abfd, &insn, addend))
+	return bfd_reloc_overflow;
+      break;
 
     case R_V850_8:
       addend += (char) bfd_get_8 (abfd, address);
@@ -1278,6 +1304,17 @@ v850_elf_perform_relocation (abfd, r_type, addend, address)
       insn |= addend;
       break;
 
+    case R_V850_LO16_SPLIT_OFFSET:
+      insn = bfd_get_32 (abfd, address);
+      result = ((insn & 0xfffe0000) >> 16) | ((insn & 0x20) >> 5);
+      if (! v850_elf_perform_lo16_relocation (abfd, &result, addend))
+	return bfd_reloc_overflow;
+      insn = (((result << 16) & 0xfffe0000)
+	      | ((result << 5) & 0x20)
+	      | (insn & ~0xfffe0020));
+      bfd_put_32 (abfd, insn, address);
+      return bfd_reloc_ok;
+
     case R_V850_ZDA_16_16_SPLIT_OFFSET:
     case R_V850_SDA_16_16_SPLIT_OFFSET:
       insn = bfd_get_32 (abfd, address);
@@ -1492,6 +1529,7 @@ v850_elf_final_link_relocate (howto, input_bfd, output_bfd,
     case R_V850_HI16_S:
     case R_V850_HI16:
     case R_V850_LO16:
+    case R_V850_LO16_SPLIT_OFFSET:
     case R_V850_16:
     case R_V850_ABS32:
     case R_V850_8:
diff --git a/bfd/libbfd.h b/bfd/libbfd.h
index 20372ddd123..8dbbd84d255 100644
--- a/bfd/libbfd.h
+++ b/bfd/libbfd.h
@@ -1299,6 +1299,7 @@ static const char *const bfd_reloc_code_real_names[] = { "@@uninitialized@@",
   "BFD_RELOC_V850_LONGCALL",
   "BFD_RELOC_V850_LONGJUMP",
   "BFD_RELOC_V850_ALIGN",
+  "BFD_RELOC_V850_LO16_SPLIT_OFFSET",
   "BFD_RELOC_MN10300_32_PCREL",
   "BFD_RELOC_MN10300_16_PCREL",
   "BFD_RELOC_TIC30_LDP",
diff --git a/bfd/reloc.c b/bfd/reloc.c
index e4fdbe316a6..96ba9c5fb72 100644
--- a/bfd/reloc.c
+++ b/bfd/reloc.c
@@ -3098,6 +3098,11 @@ ENUM
   BFD_RELOC_V850_ALIGN
 ENUMDOC
   Used to maintain alignment whilst relaxing.
+ENUM
+  BFD_RELOC_V850_LO16_SPLIT_OFFSET
+ENUMDOC
+  This is a variation of BFD_RELOC_LO16 that can be used in v850e ld.bu
+  instructions.
 ENUM
   BFD_RELOC_MN10300_32_PCREL
 ENUMDOC
diff --git a/gas/ChangeLog b/gas/ChangeLog
index e7f185928a7..392a8ec9be0 100644
--- a/gas/ChangeLog
+++ b/gas/ChangeLog
@@ -1,3 +1,9 @@
+2004-12-16  Richard Sandiford  <rsandifo@redhat.com>
+
+	* config/tc-v850.c (handle_lo16): New function.
+	(v850_reloc_prefix): Use it to check lo().
+	(md_assemble, md_apply_fix3): Handle BFD_RELOC_V850_LO16_SPLIT_OFFSET.
+
 2004-12-14  P.J. Darcy  <darcypj@us.ibm.com>
 
 	* configure.in: Add s390x-ibm-tpf support.
diff --git a/gas/config/tc-v850.c b/gas/config/tc-v850.c
index ed208ee2d2b..dfa1c7f3cfd 100644
--- a/gas/config/tc-v850.c
+++ b/gas/config/tc-v850.c
@@ -1375,6 +1375,25 @@ md_begin ()
   bfd_set_arch_mach (stdoutput, TARGET_ARCH, machine);
 }
 
+static bfd_reloc_code_real_type
+handle_lo16 (const struct v850_operand *operand)
+{
+  if (operand != NULL)
+    {
+      if (operand->bits == -1)
+	return BFD_RELOC_V850_LO16_SPLIT_OFFSET;
+
+      if (!(operand->bits == 16 && operand->shift == 16)
+	  && !(operand->bits == 15 && operand->shift == 17))
+	{
+	  as_bad (_("lo() relocation used on an instruction which does "
+		    "not support it"));
+	  return BFD_RELOC_64;  /* Used to indicate an error condition.  */
+	}
+    }
+  return BFD_RELOC_LO16;
+}
+
 static bfd_reloc_code_real_type handle_ctoff
   PARAMS ((const struct v850_operand *));
 
@@ -1516,7 +1535,7 @@ v850_reloc_prefix (operand)
 
   CHECK_ ("hi0",    BFD_RELOC_HI16	   );
   CHECK_ ("hi",	    BFD_RELOC_HI16_S	   );
-  CHECK_ ("lo",	    BFD_RELOC_LO16	   );
+  CHECK_ ("lo",	    handle_lo16 (operand)  );
   CHECK_ ("sdaoff", handle_sdaoff (operand));
   CHECK_ ("zdaoff", handle_zdaoff (operand));
   CHECK_ ("tdaoff", handle_tdaoff (operand));
@@ -1755,6 +1774,7 @@ md_assemble (str)
 		      /* Fall through.  */
 
 		    case BFD_RELOC_LO16:
+		    case BFD_RELOC_V850_LO16_SPLIT_OFFSET:
 		      {
 			/* Truncate, then sign extend the value.  */
 			ex.X_add_number = SEXT16 (ex.X_add_number);
@@ -2199,6 +2219,7 @@ md_assemble (str)
 	  switch (reloc)
 	    {
 	    case BFD_RELOC_LO16:
+	    case BFD_RELOC_V850_LO16_SPLIT_OFFSET:
 	    case BFD_RELOC_HI16:
 	    case BFD_RELOC_HI16_S:
 	      fixP->fx_no_overflow = 1;
@@ -2405,7 +2426,11 @@ md_apply_fix3 (fixP, valueP, seg)
       /* We still have to insert the value into memory!  */
       where = fixP->fx_frag->fr_literal + fixP->fx_where;
 
-      if (fixP->fx_size == 1)
+      if (fixP->fx_r_type == BFD_RELOC_V850_LO16_SPLIT_OFFSET)
+	bfd_putl32 (((value << 16) & 0xfffe0000)
+		    | ((value << 5) & 0x20)
+		    | (bfd_getl32 (where) & ~0xfffe0020), where);
+      else if (fixP->fx_size == 1)
 	*where = value & 0xff;
       else if (fixP->fx_size == 2)
 	bfd_putl16 (value & 0xffff, (unsigned char *) where);
diff --git a/gas/testsuite/ChangeLog b/gas/testsuite/ChangeLog
index 56c056ec827..3b19181a7a0 100644
--- a/gas/testsuite/ChangeLog
+++ b/gas/testsuite/ChangeLog
@@ -1,3 +1,8 @@
+2004-12-16  Richard Sandiford  <rsandifo@redhat.com>
+
+	* gas/v850/split-lo16.{s,d}: New test.
+	* gas/v850/v850.exp: Run it.
+
 2004-12-15 Jan Beulich  <jbeulich@novell.com>
 
 	* gas/elf/section5.[els]: New.
diff --git a/gas/testsuite/gas/v850/basic.exp b/gas/testsuite/gas/v850/basic.exp
index e3b0713bae8..1cf251ee811 100644
--- a/gas/testsuite/gas/v850/basic.exp
+++ b/gas/testsuite/gas/v850/basic.exp
@@ -436,4 +436,5 @@ if [istarget v850*-*-*] then {
 
     gas_test_error "range.s" "-mwarn-signed-overflow" "Check for range error on byte load/store"
     run_dump_test "v850e1"
+    run_dump_test "split-lo16"
 }
diff --git a/gas/testsuite/gas/v850/split-lo16.d b/gas/testsuite/gas/v850/split-lo16.d
new file mode 100644
index 00000000000..9503ac9d8d0
--- /dev/null
+++ b/gas/testsuite/gas/v850/split-lo16.d
@@ -0,0 +1,16 @@
+#objdump: -dr
+#name: V850E split LO16 tests
+#as: -mv850e
+#...
+00000000 <.*>:
+   0:	40 0e 00 00 	movhi	0, r0, r1
+			2: R_V850_HI16_S	foo
+   4:	01 16 00 00 	addi	0, r1, r2
+			6: R_V850_LO16	foo
+   8:	01 17 00 00 	ld\.b	0\[r1\],r2
+			a: R_V850_LO16	foo
+   c:	81 17 01 00 	ld\.bu	0\[r1\],r2
+			c: R_V850_LO16_SPLIT_OFFSET	foo
+  10:	a1 17 45 23 	ld\.bu	9029\[r1\],r2
+  14:	81 17 57 34 	ld\.bu	13398\[r1\],r2
+#pass
diff --git a/gas/testsuite/gas/v850/split-lo16.s b/gas/testsuite/gas/v850/split-lo16.s
new file mode 100644
index 00000000000..fc3afd14e6e
--- /dev/null
+++ b/gas/testsuite/gas/v850/split-lo16.s
@@ -0,0 +1,7 @@
+	movhi	hi(foo),r0,r1
+	addi	lo(foo),r1,r2
+	ld.b	lo(foo),r1,r2
+	ld.bu	lo(foo),r1,r2
+
+	ld.bu	lo(0x12345),r1,r2
+	ld.bu	lo(0x123456),r1,r2
diff --git a/include/elf/ChangeLog b/include/elf/ChangeLog
index f4f961d9b2f..ea49947f593 100644
--- a/include/elf/ChangeLog
+++ b/include/elf/ChangeLog
@@ -1,3 +1,7 @@
+2004-12-16  Richard Sandiford  <rsandifo@redhat.com>
+
+	* v850.h (R_V850_LO16_SPLIT_OFFSET): New reloc.
+
 2004-12-09  Ian Lance Taylor  <ian@wasabisystems.com>
 
 	* mips.h (E_MIPS_MACH_9000): Define.
diff --git a/include/elf/v850.h b/include/elf/v850.h
index 7d5110b3342..f42900532c5 100644
--- a/include/elf/v850.h
+++ b/include/elf/v850.h
@@ -79,6 +79,7 @@ START_RELOC_NUMBERS (v850_reloc_type)
      RELOC_NUMBER (R_V850_LONGJUMP, 26)
      RELOC_NUMBER (R_V850_ALIGN, 27)
      RELOC_NUMBER (R_V850_REL32, 28)
+     RELOC_NUMBER (R_V850_LO16_SPLIT_OFFSET, 29)	/* For ld.bu */
 END_RELOC_NUMBERS (R_V850_max)
 
 
diff --git a/ld/testsuite/ChangeLog b/ld/testsuite/ChangeLog
index cbf50fcdc76..d817a227bd4 100644
--- a/ld/testsuite/ChangeLog
+++ b/ld/testsuite/ChangeLog
@@ -1,3 +1,7 @@
+2004-12-16  Richard Sandiford  <rsandifo@redhat.com>
+
+	* ld-v850: New directory.
+
 2004-12-14  Richard Sandiford  <rsandifo@redhat.com>
 
 	* ld-mips-elf/jal-overflow-2.[sd]: New test.
diff --git a/ld/testsuite/ld-v850/split-lo16.d b/ld/testsuite/ld-v850/split-lo16.d
new file mode 100644
index 00000000000..c5668ffaf73
--- /dev/null
+++ b/ld/testsuite/ld-v850/split-lo16.d
@@ -0,0 +1,26 @@
+#source: split-lo16.s -mv850e
+#ld: -Tsplit-lo16.ld
+#objdump: -d
+#...
+00010000 <.*>:
+   10000:	40 0e 34 12 	movhi	4660, r0, r1
+   10004:	01 16 78 56 	addi	22136, r1, r2
+   10008:	81 17 79 56 	ld\.bu	22136\[r1\],r2
+   1000c:	40 0e 36 12 	movhi	4662, r0, r1
+   10010:	01 16 78 d8 	addi	-10120, r1, r2
+   10014:	81 17 79 d8 	ld\.bu	-10120\[r1\],r2
+   10018:	40 0e 12 00 	movhi	18, r0, r1
+   1001c:	81 17 57 34 	ld\.bu	13398\[r1\],r2
+   10020:	01 16 56 34 	addi	13398, r1, r2
+   10024:	40 0e 14 00 	movhi	20, r0, r1
+   10028:	81 17 57 b6 	ld\.bu	-18858\[r1\],r2
+   1002c:	01 16 56 b6 	addi	-18858, r1, r2
+   10030:	40 0e 79 56 	movhi	22137, r0, r1
+   10034:	01 16 bc 9a 	addi	-25924, r1, r2
+   10038:	81 17 bd 9a 	ld\.bu	-25924\[r1\],r2
+   1003c:	40 0e 9b 78 	movhi	30875, r0, r1
+   10040:	81 17 df bc 	ld\.bu	-17186\[r1\],r2
+   10044:	01 16 de bc 	addi	-17186, r1, r2
+   10048:	40 0e 45 23 	movhi	9029, r0, r1
+   1004c:	a1 17 89 67 	ld\.bu	26505\[r1\],r2
+#pass
diff --git a/ld/testsuite/ld-v850/split-lo16.ld b/ld/testsuite/ld-v850/split-lo16.ld
new file mode 100644
index 00000000000..bde906303b0
--- /dev/null
+++ b/ld/testsuite/ld-v850/split-lo16.ld
@@ -0,0 +1,11 @@
+SECTIONS
+{
+  lo16_carry = 0x56789abc;
+  lo16_nocarry = 0x12345678;
+  split_lo16_carry = 0x789abcde;
+  split_lo16_nocarry = 0x00123456;
+  odd = 0x23456789;
+  . = 0x10000;
+  .text : { *(.text); }
+  /DISCARD/ : { *(*); }
+}
diff --git a/ld/testsuite/ld-v850/split-lo16.s b/ld/testsuite/ld-v850/split-lo16.s
new file mode 100644
index 00000000000..7eaae412445
--- /dev/null
+++ b/ld/testsuite/ld-v850/split-lo16.s
@@ -0,0 +1,20 @@
+	movhi	hi(lo16_nocarry),r0,r1
+	addi	lo(lo16_nocarry),r1,r2
+	ld.bu	lo(lo16_nocarry)[r1],r2
+	movhi	hi(lo16_nocarry + 0x18200),r0,r1
+	addi	lo(lo16_nocarry + 0x18200),r1,r2
+	ld.bu	lo(lo16_nocarry + 0x18200)[r1],r2
+	movhi	hi(split_lo16_nocarry),r0,r1
+	ld.bu	lo(split_lo16_nocarry)[r1],r2
+	addi	lo(split_lo16_nocarry),r1,r2
+	movhi	hi(split_lo16_nocarry + 0x18200),r0,r1
+	ld.bu	lo(split_lo16_nocarry + 0x18200)[r1],r2
+	addi	lo(split_lo16_nocarry + 0x18200),r1,r2
+	movhi	hi(lo16_carry),r0,r1
+	addi	lo(lo16_carry),r1,r2
+	ld.bu	lo(lo16_carry)[r1],r2
+	movhi	hi(split_lo16_carry),r0,r1
+	ld.bu	lo(split_lo16_carry)[r1],r2
+	addi	lo(split_lo16_carry),r1,r2
+	movhi	hi(odd),r0,r1
+	ld.bu	lo(odd)[r1],r2
diff --git a/ld/testsuite/ld-v850/v850.exp b/ld/testsuite/ld-v850/v850.exp
new file mode 100644
index 00000000000..59f184ac878
--- /dev/null
+++ b/ld/testsuite/ld-v850/v850.exp
@@ -0,0 +1,3 @@
+if [istarget v850*-*-*] {
+    run_dump_test "split-lo16"
+}