This patch adds __flashx as a new named address space that allocates
objects in .progmemx.data.  The handling is mostly the same or similar
to that of 24-bit space __memx, except that the output routines are
simpler and more efficient.  Loads are emit inline when ELPMX or
LPMX is available.  The address space uses a 24-bit addresses even
on devices with a program memory size of 64 KiB or less.

Passes without new regressions.

Also passes without new regressions when used as AS in the proposed
new target hook TARGET_ADDR_SPACE_FOR_ARTIFICIAL_RODATA, cf.
https://gcc.gnu.org/pipermail/gcc-patches/2024-December/671216.html

Ok for trunk?

Johann

--


AVR: Add __flashx as 24-bit named address space.

This patch adds __flashx as a new named address space that allocates
objects in .progmemx.data.  The handling is mostly the same or similar
to that of 24-bit space __memx, except that the output routines are
simpler and more efficient.  Loads are emit inline when ELPMX or
LPMX is available.  The address space uses a 24-bit addresses even
on devices with a program memory size of 64 KiB or less.

gcc/
        * doc/extend.texi (AVR Named Address Spaces): Document __flashx.
        * config/avr/avr.h (ADDR_SPACE_FLASHX): New enum value.
        * config/avr/avr-protos.h (avr_out_fload, avr_mem_flashx_p)
        (avr_fload_libgcc_p, avr_load_libgcc_mem_p)
        (avr_load_libgcc_insn_p): New.
        * config/avr/avr.cc (avr_addrspace): Add ADDR_SPACE_FLASHX.
        (avr_decl_flashx_p, avr_mem_flashx_p, avr_fload_libgcc_p)
        (avr_load_libgcc_mem_p, avr_load_libgcc_insn_p, avr_out_fload):
        New functions.
        (avr_adjust_insn_length) [ADJUST_LEN_FLOAD]: Handle case.
        (avr_progmem_p) [avr_decl_flashx_p]: return 2.
        (avr_addr_space_legitimate_address_p) [ADDR_SPACE_FLASHX]:
        Has same behavior like ADDR_SPACE_MEMX.
        (avr_addr_space_convert): Use pointer sizes rather then ASes.
        (avr_addr_space_contains): New function.
        (avr_convert_to_type): Use it.
        (avr_emit_cpymemhi): Handle ADDR_SPACE_FLASHX.
        * config/avr/avr.md (adjust_len) <fload>: New attr value.
        (gen_load<mode>_libgcc): Renamed from load<mode>_libgcc.
        (xload8<mode>_A): Iterate over MOVMODE rather than over ALL1.
        (fxmov<mode>_A): New from xloadv<mode>_A.
        (xmov<mode>_8): New from xload<mode>_A.
        (fmov<mode>): New insns.
        (fxload<mode>_A): New from xload<mode>_A.
        (fxload_<mode>_libgcc): New from xload_<mode>_libgcc.
        (*fxload_<mode>_libgcc): New from *xload_<mode>_libgcc.
        (mov<mode>) [avr_mem_flashx_p]: Hande ADDR_SPACE_FLASHX.
        (cpymemx_<mode>): Make sure the address space is not lost
        when splitting.
        (*cpymemx_<mode>) [ADDR_SPACE_FLASHX]: Use __movmemf_<mode> for asm.
        (*ashlqi.1.zextpsi_split): New combine pattern.
        * config/avr/predicates.md (nox_general_operand): Don't match
        when avr_mem_flashx_p is true.
        * config/avr/avr-passes.cc (AVR_LdSt_Props):
        ADDR_SPACE_FLASHX has no post_inc.

gcc/testsuite/
        * gcc.target/avr/torture/addr-space-1.h [AVR_HAVE_ELPM]:
        Use a function to bump .progmemx.data to a high address.
        * gcc.target/avr/torture/addr-space-2.h: Same.
        * gcc.target/avr/torture/addr-space-1-fx.c: New test.
        * gcc.target/avr/torture/addr-space-2-fx.c: New test.

libgcc/
        * config/avr/t-avr (LIB1ASMFUNCS): Add _fload_1, _fload_2,
        _fload_3, _fload_4, _movmemf.
        * config/avr/lib1funcs.S (.branch_plus): New .macro.
        (__xload_1, __xload_2, __xload_3, __xload_4): When the address is
        located in flash, then forward to...
        (__fload_1, __fload_2, __fload_3, __fload_4): ...these new
        functions, respectively.
        (__movmemx_hi): When the address is located in flash, forward to...
        (__movmemf_hi): ...this new function.
    AVR: Add __flashx as 24-bit named address space.
    
    This patch adds __flashx as a new named address space that allocates
    objects in .progmemx.data.  The handling is mostly the same or similar
    to that of 24-bit space __memx, except that the output routines are
    simpler and more efficient.  Loads are emit inline when ELPMX or
    LPMX is available.  The address space uses a 24-bit addresses even
    on devices with a program memory size of 64 KiB or less.
    
    gcc/
            * doc/extend.texi (AVR Named Address Spaces): Document __flashx.
            * config/avr/avr.h (ADDR_SPACE_FLASHX): New enum value.
            * config/avr/avr-protos.h (avr_out_fload, avr_mem_flashx_p)
            (avr_fload_libgcc_p, avr_load_libgcc_mem_p)
            (avr_load_libgcc_insn_p): New.
            * config/avr/avr.cc (avr_addrspace): Add ADDR_SPACE_FLASHX.
            (avr_decl_flashx_p, avr_mem_flashx_p, avr_fload_libgcc_p)
            (avr_load_libgcc_mem_p, avr_load_libgcc_insn_p, avr_out_fload):
            New functions.
            (avr_adjust_insn_length) [ADJUST_LEN_FLOAD]: Handle case.
            (avr_progmem_p) [avr_decl_flashx_p]: return 2.
            (avr_addr_space_legitimate_address_p) [ADDR_SPACE_FLASHX]:
            Has same behavior like ADDR_SPACE_MEMX.
            (avr_addr_space_convert): Use pointer sizes rather then ASes.
            (avr_addr_space_contains): New function.
            (avr_convert_to_type): Use it.
            (avr_emit_cpymemhi): Handle ADDR_SPACE_FLASHX.
            * config/avr/avr.md (adjust_len) <fload>: New attr value.
            (gen_load<mode>_libgcc): Renamed from load<mode>_libgcc.
            (xload8<mode>_A): Iterate over MOVMODE rather than over ALL1.
            (fxmov<mode>_A): New from xloadv<mode>_A.
            (xmov<mode>_8): New from xload<mode>_A.
            (fmov<mode>): New insns.
            (fxload<mode>_A): New from xload<mode>_A.
            (fxload_<mode>_libgcc): New from xload_<mode>_libgcc.
            (*fxload_<mode>_libgcc): New from *xload_<mode>_libgcc.
            (mov<mode>) [avr_mem_flashx_p]: Hande ADDR_SPACE_FLASHX.
            (cpymemx_<mode>): Make sure the address space is not lost
            when splitting.
            (*cpymemx_<mode>) [ADDR_SPACE_FLASHX]: Use __movmemf_<mode> for asm.
            (*ashlqi.1.zextpsi_split): New combine pattern.
            * config/avr/predicates.md (nox_general_operand): Don't match
            when avr_mem_flashx_p is true.
            * config/avr/avr-passes.cc (AVR_LdSt_Props):
            ADDR_SPACE_FLASHX has no post_inc.
    
    gcc/testsuite/
            * gcc.target/avr/torture/addr-space-1.h [AVR_HAVE_ELPM]:
            Use a function to bump .progmemx.data to a high address.
            * gcc.target/avr/torture/addr-space-2.h: Same.
            * gcc.target/avr/torture/addr-space-1-fx.c: New test.
            * gcc.target/avr/torture/addr-space-2-fx.c: New test.
    
    libgcc/
            * config/avr/t-avr (LIB1ASMFUNCS): Add _fload_1, _fload_2,
            _fload_3, _fload_4, _movmemf.
            * config/avr/lib1funcs.S (.branch_plus): New .macro.
            (__xload_1, __xload_2, __xload_3, __xload_4): When the address is
            located in flash, then forward to...
            (__fload_1, __fload_2, __fload_3, __fload_4): ...these new
            functions, respectively.
            (__movmemx_hi): When the address is located in flash, forward to...
            (__movmemf_hi): ...this new function.

diff --git a/gcc/config/avr/avr-passes.cc b/gcc/config/avr/avr-passes.cc
index fad64b1b345..8923bdbf124 100644
--- a/gcc/config/avr/avr-passes.cc
+++ b/gcc/config/avr/avr-passes.cc
@@ -4359,7 +4359,8 @@ struct AVR_LdSt_Props
   AVR_LdSt_Props (int regno, bool store_p, bool volatile_p, addr_space_t as)
   {
     bool generic_p = ADDR_SPACE_GENERIC_P (as);
-    bool flashx_p = ! generic_p && as != ADDR_SPACE_MEMX;
+    bool flashx_p = (! generic_p
+		     && as != ADDR_SPACE_MEMX && as != ADDR_SPACE_FLASHX);
     has_postinc = generic_p || (flashx_p && regno == REG_Z);
     has_predec = generic_p;
     has_ldd = ! AVR_TINY && generic_p && (regno == REG_Y || regno == REG_Z);
diff --git a/gcc/config/avr/avr-protos.h b/gcc/config/avr/avr-protos.h
index 0a441124387..b93f2b37a72 100644
--- a/gcc/config/avr/avr-protos.h
+++ b/gcc/config/avr/avr-protos.h
@@ -113,6 +113,7 @@ extern const char* avr_out_add_msb (rtx_insn*, rtx*, rtx_code, int*);
 extern const char* avr_out_round (rtx_insn *, rtx*, int* =NULL);
 extern const char* avr_out_addto_sp (rtx*, int*);
 extern const char* avr_out_xload (rtx_insn *, rtx*, int*);
+extern const char* avr_out_fload (rtx_insn *, rtx*, int*);
 extern const char* avr_out_cpymem (rtx_insn *, rtx*, int*);
 extern const char* avr_out_insert_bits (rtx*, int*);
 extern bool avr_popcount_each_byte (rtx, int, int);
@@ -144,9 +145,13 @@ extern rtx avr_incoming_return_addr_rtx (void);
 extern rtx avr_legitimize_reload_address (rtx*, machine_mode, int, int, int, int, rtx (*)(rtx,int));
 extern bool avr_adiw_reg_p (rtx);
 extern bool avr_mem_flash_p (rtx);
+extern bool avr_mem_flashx_p (rtx);
 extern bool avr_mem_memx_p (rtx);
 extern bool avr_load_libgcc_p (rtx);
 extern bool avr_xload_libgcc_p (machine_mode);
+extern bool avr_fload_libgcc_p (machine_mode);
+extern bool avr_load_libgcc_mem_p (rtx, addr_space_t, bool use_libgcc);
+extern bool avr_load_libgcc_insn_p (rtx_insn *, addr_space_t, bool use_libgcc);
 extern rtx avr_eval_addr_attrib (rtx x);
 
 extern bool avr_float_lib_compare_returns_bool (machine_mode, rtx_code);
diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc
index 7dc3eb2016a..f03e7ffae8f 100644
--- a/gcc/config/avr/avr.cc
+++ b/gcc/config/avr/avr.cc
@@ -115,6 +115,7 @@ const avr_addrspace_t avr_addrspace[ADDR_SPACE_COUNT] =
   { ADDR_SPACE_FLASH3, 1, 2, "__flash3",  3, ".progmem3.data" },
   { ADDR_SPACE_FLASH4, 1, 2, "__flash4",  4, ".progmem4.data" },
   { ADDR_SPACE_FLASH5, 1, 2, "__flash5",  5, ".progmem5.data" },
+  { ADDR_SPACE_FLASHX, 1, 3, "__flashx",  0, ".progmemx.data" },
   { ADDR_SPACE_MEMX, 1, 3, "__memx",  0, ".progmemx.data" },
 };
 
@@ -690,7 +691,7 @@ avr_decl_flash_p (tree decl)
 
 
 /* Return TRUE if DECL is a VAR_DECL located in the 24-bit flash
-   address space and FALSE, otherwise.  */
+   address space __memx and FALSE, otherwise.  */
 
 static bool
 avr_decl_memx_p (tree decl)
@@ -705,6 +706,22 @@ avr_decl_memx_p (tree decl)
 }
 
 
+/* Return TRUE if DECL is a VAR_DECL located in the 24-bit flash
+   address space __flashx and FALSE, otherwise.  */
+
+static bool
+avr_decl_flashx_p (tree decl)
+{
+  if (TREE_CODE (decl) != VAR_DECL
+      || TREE_TYPE (decl) == error_mark_node)
+    {
+      return false;
+    }
+
+  return ADDR_SPACE_FLASHX == TYPE_ADDR_SPACE (TREE_TYPE (decl));
+}
+
+
 /* Return TRUE if X is a MEM rtx located in flash and FALSE, otherwise.  */
 
 bool
@@ -715,8 +732,8 @@ avr_mem_flash_p (rtx x)
 }
 
 
-/* Return TRUE if X is a MEM rtx located in the 24-bit flash
-   address space and FALSE, otherwise.  */
+/* Return TRUE if X is a MEM rtx located in the 24-bit
+   address space __memx and FALSE, otherwise.  */
 
 bool
 avr_mem_memx_p (rtx x)
@@ -726,6 +743,17 @@ avr_mem_memx_p (rtx x)
 }
 
 
+/* Return TRUE if X is a MEM rtx located in the 24-bit flash
+   address space __flashx and FALSE, otherwise.  */
+
+bool
+avr_mem_flashx_p (rtx x)
+{
+  return (MEM_P (x)
+	  && ADDR_SPACE_FLASHX == MEM_ADDR_SPACE (x));
+}
+
+
 /* A helper for the subsequent function attribute used to dig for
    attribute 'name' in a FUNCTION_DECL or FUNCTION_TYPE.  */
 
@@ -3248,7 +3276,8 @@ avr_load_libgcc_p (rtx op)
 }
 
 
-/* Return true if a value of mode MODE is read by __xload_* function.  */
+/* Return true if a value of mode MODE is read by __xload_* function
+   provided it is located in __memx.  */
 
 bool
 avr_xload_libgcc_p (machine_mode mode)
@@ -3260,6 +3289,66 @@ avr_xload_libgcc_p (machine_mode mode)
 }
 
 
+/* Return true if a value of mode MODE is read by __fload_* function
+   provided it is located in __flashx.  */
+
+bool
+avr_fload_libgcc_p (machine_mode)
+{
+  return (! AVR_HAVE_ELPMX
+	  && ! AVR_HAVE_LPMX);
+}
+
+
+/* USE_LIBGCC = true:  Return true when MEM is a mem rtx for address space
+   AS that will be loaded using a libgcc support function.
+   USE_LIBGCC = false:  Return true when MEM is a mem rtx for address space
+   AS that will be loaded inline (without using a libgcc support function).  */
+
+bool
+avr_load_libgcc_mem_p (rtx mem, addr_space_t as, bool use_libgcc)
+{
+  if (MEM_P (mem))
+    {
+      machine_mode mode = GET_MODE (mem);
+      rtx addr = XEXP (mem, 0);
+
+      if (MEM_ADDR_SPACE (mem) != as
+	  || GET_MODE (addr) != targetm.addr_space.pointer_mode (as))
+	return false;
+
+      switch (as)
+	{
+	default:
+	  gcc_unreachable ();
+
+	case ADDR_SPACE_FLASH:
+	  return avr_load_libgcc_p (mem) == use_libgcc;
+
+	case ADDR_SPACE_MEMX:
+	  return avr_xload_libgcc_p (mode) == use_libgcc;
+
+	case ADDR_SPACE_FLASHX:
+	  return avr_fload_libgcc_p (mode) == use_libgcc;
+	}
+    }
+
+  return false;
+}
+
+
+/* Like `avr_load_libgcc_mem_p()', but for a single_set insn with
+   a SET_SRC according to avr_load_libgcc_mem_p.  */
+
+bool
+avr_load_libgcc_insn_p (rtx_insn *insn, addr_space_t as, bool use_libgcc)
+{
+  rtx set = single_set (insn);
+  return (set
+	  && avr_load_libgcc_mem_p (SET_SRC (set), as, use_libgcc));
+}
+
+
 /* Return true when INSN has a REG_UNUSED note for hard reg REG.
    rtlanal.cc::find_reg_note() uses == to compare XEXP (link, 0)
    therefore use a custom function.  */
@@ -3721,7 +3810,9 @@ avr_out_lpm (rtx_insn *insn, rtx *op, int *plen)
 }
 
 
-/* Worker function for xload_8 insn.  */
+/* Load a value from 24-bit address space __memx and return "".
+   PLEN == 0: Output instructions.
+   PLEN != 0: Set *PLEN to the length of the sequence in words.  */
 
 const char *
 avr_out_xload (rtx_insn * /*insn*/, rtx *op, int *plen)
@@ -3751,6 +3842,53 @@ avr_out_xload (rtx_insn * /*insn*/, rtx *op, int *plen)
 }
 
 
+/* Load a value from 24-bit address space __flashx and return "".
+   PLEN == 0: Output instructions.
+   PLEN != 0: Set *PLEN to the length of the sequence in words.  */
+
+const char *
+avr_out_fload (rtx_insn * /*insn*/, rtx *xop, int *plen)
+{
+  gcc_assert (AVR_HAVE_ELPMX
+	      || (! AVR_HAVE_ELPM && AVR_HAVE_LPMX));
+  if (plen)
+    *plen = 0;
+
+  if (AVR_HAVE_ELPMX)
+    avr_asm_len ("out __RAMPZ__,%1", xop, plen, 1);
+
+  const int n_bytes = GET_MODE_SIZE (GET_MODE (xop[0]));
+  const char *s_load = AVR_HAVE_ELPMX ? "elpm %0,Z" : "lpm %0,Z";
+  const char *s_load_inc = AVR_HAVE_ELPMX ? "elpm %0,Z+" : "lpm %0,Z+";
+  const char *s_load_tmp_inc = AVR_HAVE_ELPMX ? "elpm r0,Z+" : "lpm r0,Z+";
+  bool use_tmp_for_r30 = false;
+
+  // There are nasty cases where reload assigns a register to dest that
+  // overlaps Z, even though fmov<mode> clobbers REG_Z.
+  for (int i = 0; i < n_bytes; ++i)
+    {
+      rtx b = avr_byte (xop[0], i);
+      if (i == n_bytes - 1)
+	avr_asm_len (s_load, &b, plen, 1);
+      else if (REGNO (b) == REG_30)
+	{
+	  avr_asm_len (s_load_tmp_inc, &b, plen, 1);
+	  use_tmp_for_r30 = true;
+	}
+      else
+	avr_asm_len (s_load_inc, &b, plen, 1);
+    }
+
+  if (use_tmp_for_r30)
+    avr_asm_len ("mov r30,r0", xop, plen, 1);
+
+  if (AVR_HAVE_ELPMX && AVR_HAVE_RAMPD)
+    avr_asm_len ("out __RAMPZ__,__zero_reg__", xop, plen, 1);
+
+  return "";
+}
+
+
 /* A helper for `output_reload_insisf' and `output_reload_inhi'.  */
 /* Set register OP[0] to compile-time constant OP[1].
    CLOBBER_REG is a QI clobber register or NULL_RTX.
@@ -10770,6 +10908,7 @@ avr_adjust_insn_length (rtx_insn *insn, int len)
     case ADJUST_LEN_MOV32: output_movsisf (insn, op, &len); break;
     case ADJUST_LEN_CPYMEM: avr_out_cpymem (insn, op, &len); break;
     case ADJUST_LEN_XLOAD: avr_out_xload (insn, op, &len); break;
+    case ADJUST_LEN_FLOAD: avr_out_fload (insn, op, &len); break;
     case ADJUST_LEN_SEXT: avr_out_sign_extend (insn, op, &len); break;
 
     case ADJUST_LEN_SFRACT: avr_out_fract (insn, op, true, &len); break;
@@ -11207,7 +11346,8 @@ avr_progmem_p (tree decl, tree attributes)
   if (TREE_CODE (decl) != VAR_DECL)
     return 0;
 
-  if (avr_decl_memx_p (decl))
+  if (avr_decl_memx_p (decl)
+      || avr_decl_flashx_p (decl))
     return 2;
 
   if (avr_decl_flash_p (decl))
@@ -14160,6 +14300,7 @@ avr_addr_space_legitimate_address_p (machine_mode mode, rtx x, bool strict,
       break; /* FLASH */
 
     case ADDR_SPACE_MEMX:
+    case ADDR_SPACE_FLASHX:
       if (REG_P (x))
 	ok = (!strict
 	      && can_create_pseudo_p ());
@@ -14175,7 +14316,7 @@ avr_addr_space_legitimate_address_p (machine_mode mode, rtx x, bool strict,
 		&& REGNO (lo) == REG_Z);
 	}
 
-      break; /* MEMX */
+      break; /* MEMX, FLASHX */
     }
 
   if (avr_log.legitimate_address_p)
@@ -14223,19 +14364,20 @@ avr_addr_space_legitimize_address (rtx x, rtx old_x,
 /* Implement `TARGET_ADDR_SPACE_CONVERT'.  */
 
 static rtx
-avr_addr_space_convert (rtx src, tree type_from, tree type_to)
+avr_addr_space_convert (rtx src, tree type_old, tree type_new)
 {
-  addr_space_t as_from = TYPE_ADDR_SPACE (TREE_TYPE (type_from));
-  addr_space_t as_to = TYPE_ADDR_SPACE (TREE_TYPE (type_to));
+  addr_space_t as_old = TYPE_ADDR_SPACE (TREE_TYPE (type_old));
+  addr_space_t as_new = TYPE_ADDR_SPACE (TREE_TYPE (type_new));
+  int size_old = GET_MODE_SIZE (targetm.addr_space.pointer_mode (as_old));
+  int size_new = GET_MODE_SIZE (targetm.addr_space.pointer_mode (as_new));
 
   if (avr_log.progmem)
     avr_edump ("\n%!: op = %r\nfrom = %t\nto = %t\n",
-	       src, type_from, type_to);
+	       src, type_old, type_new);
 
   /* Up-casting from 16-bit to 24-bit pointer.  */
 
-  if (as_from != ADDR_SPACE_MEMX
-      && as_to == ADDR_SPACE_MEMX)
+  if (size_old == 2 && size_new == 3)
     {
       rtx sym = src;
       rtx reg = gen_reg_rtx (PSImode);
@@ -14252,14 +14394,16 @@ avr_addr_space_convert (rtx src, tree type_from, tree type_to)
       if (SYMBOL_REF_P (sym)
 	  && ADDR_SPACE_FLASH == AVR_SYMBOL_GET_ADDR_SPACE (sym))
 	{
-	  as_from = ADDR_SPACE_FLASH;
+	  as_old = ADDR_SPACE_FLASH;
 	}
 
-      /* Linearize memory: RAM has bit 23 set.  */
+      /* Linearize memory: RAM has bit 23 set.  When as_new = __flashx then
+	 this is basically UB since __flashx mistreats RAM addresses, but there
+	 is no way to bail out.  (Though -Waddr-space-convert will tell.)  */
 
-      int msb = ADDR_SPACE_GENERIC_P (as_from)
+      int msb = ADDR_SPACE_GENERIC_P (as_old)
 	? 0x80
-	: avr_addrspace[as_from].segment;
+	: avr_addrspace[as_old].segment;
 
       src = force_reg (Pmode, src);
 
@@ -14272,8 +14416,7 @@ avr_addr_space_convert (rtx src, tree type_from, tree type_to)
 
   /* Down-casting from 24-bit to 16-bit throws away the high byte.  */
 
-  if (as_from == ADDR_SPACE_MEMX
-      && as_to != ADDR_SPACE_MEMX)
+  if (size_old == 3 && size_new == 2)
     {
       rtx new_src = gen_reg_rtx (Pmode);
 
@@ -14299,6 +14442,18 @@ avr_addr_space_subset_p (addr_space_t /*subset*/, addr_space_t /*superset*/)
 }
 
 
+/* Helps the next function.  */
+
+static bool
+avr_addr_space_contains (addr_space_t super, addr_space_t sub)
+{
+  return (super == sub
+	  || super == ADDR_SPACE_MEMX
+	  || (super == ADDR_SPACE_FLASHX
+	      && sub != ADDR_SPACE_MEMX && ! ADDR_SPACE_GENERIC_P (sub)));
+}
+
+
 /* Implement `TARGET_CONVERT_TO_TYPE'.  */
 
 static tree
@@ -14339,8 +14494,7 @@ avr_convert_to_type (tree type, tree expr)
       if (avr_log.progmem)
 	avr_edump ("%?: type = %t\nexpr = %t\n\n", type, expr);
 
-      if (as_new != ADDR_SPACE_MEMX
-	  && as_new != as_old)
+      if (! avr_addr_space_contains (as_new, as_old))
 	{
 	  location_t loc = EXPR_LOCATION (expr);
 	  const char *name_old = avr_addrspace[as_old].name;
@@ -14524,9 +14678,18 @@ avr_emit_cpymemhi (rtx *xop)
   rtx a_src  = XEXP (xop[1], 0);
   rtx a_dest = XEXP (xop[0], 0);
 
-  if (PSImode == GET_MODE (a_src))
+  if (as == ADDR_SPACE_FLASHX
+      && ! AVR_HAVE_ELPM)
+    {
+      a_src = copy_to_mode_reg (Pmode, avr_word (a_src, 0));
+      as = ADDR_SPACE_FLASH;
+    }
+
+  machine_mode addr_mode = GET_MODE (a_src);
+
+  if (addr_mode == PSImode)
     {
-      gcc_assert (as == ADDR_SPACE_MEMX);
+      gcc_assert (as == ADDR_SPACE_MEMX || as == ADDR_SPACE_FLASHX);
 
       loop_mode = (count < 0x100) ? QImode : HImode;
       loop_reg = gen_rtx_REG (loop_mode, 24);
@@ -14574,7 +14737,7 @@ avr_emit_cpymemhi (rtx *xop)
 
   gcc_assert (TMP_REGNO == LPM_REGNO);
 
-  if (as != ADDR_SPACE_MEMX)
+  if (addr_mode == HImode)
     {
       /* Load instruction ([E]LPM or LD) is known at compile time:
 	 Do the copy-loop inline.  */
@@ -14589,7 +14752,7 @@ avr_emit_cpymemhi (rtx *xop)
       rtx (*fun) (rtx, rtx)
 	= QImode == loop_mode ? gen_cpymemx_qi : gen_cpymemx_hi;
 
-      emit_move_insn (gen_rtx_REG (QImode, 23), a_hi8);
+      emit_move_insn (gen_rtx_REG (QImode, REG_23), a_hi8);
 
       insn = fun (xas, GEN_INT (avr_addr.rampz));
     }
diff --git a/gcc/config/avr/avr.h b/gcc/config/avr/avr.h
index b9c94bf6fc1..b0ae94cb301 100644
--- a/gcc/config/avr/avr.h
+++ b/gcc/config/avr/avr.h
@@ -53,6 +53,7 @@ enum
     ADDR_SPACE_FLASH3,
     ADDR_SPACE_FLASH4,
     ADDR_SPACE_FLASH5,
+    ADDR_SPACE_FLASHX,
     ADDR_SPACE_MEMX,
     /* Sentinel */
     ADDR_SPACE_COUNT
diff --git a/gcc/config/avr/avr.md b/gcc/config/avr/avr.md
index 9c348be7c48..02e9ec71ac8 100644
--- a/gcc/config/avr/avr.md
+++ b/gcc/config/avr/avr.md
@@ -164,7 +164,7 @@ (define_attr "adjust_len"
    tsthi, tstpsi, tstsi, compare, compare64, call,
    mov8, mov16, mov24, mov32, reload_in16, reload_in24, reload_in32,
    ufract, sfract, round,
-   xload, cpymem,
+   xload, fload, cpymem,
    ashlqi, ashrqi, lshrqi,
    ashlhi, ashrhi, lshrhi,
    ashlsi, ashrsi, lshrsi,
@@ -532,12 +532,8 @@ (define_split
 ;;========================================================================
 ;; Move stuff around
 
-;; "loadqi_libgcc"
-;; "loadhi_libgcc"
-;; "loadpsi_libgcc"
-;; "loadsi_libgcc"
-;; "loadsf_libgcc"
-(define_expand "load<mode>_libgcc"
+;; Expand helper for mov<mode>.
+(define_expand "gen_load<mode>_libgcc"
   [(set (match_dup 3)
         (match_dup 2))
    (set (reg:MOVMODE 22)
@@ -581,24 +577,25 @@ (define_insn "*load_<mode>_libgcc"
   [(set_attr "type" "xcall")])
 
 
-;; "xload8qi_A"
-;; "xload8qq_A" "xload8uqq_A"
-(define_insn_and_split "xload8<mode>_A"
-  [(set (match_operand:ALL1 0 "register_operand" "=r")
-        (match_operand:ALL1 1 "memory_operand"    "m"))
+;; Inline load a __memx value when flash <= 64 KiB, or
+;; inline load a __flashx value.
+(define_insn_and_split "fxmov<mode>_A"
+  [(set (match_operand:MOVMODE 0 "register_operand" "=r")
+        (match_operand:MOVMODE 1 "memory_operand"    "m"))
    (clobber (reg:HI REG_Z))]
   "can_create_pseudo_p()
-   && !avr_xload_libgcc_p (<MODE>mode)
-   && avr_mem_memx_p (operands[1])
-   && REG_P (XEXP (operands[1], 0))"
+   && REG_P (XEXP (operands[1], 0))
+   && (avr_load_libgcc_insn_p (insn, ADDR_SPACE_MEMX, false)
+       || avr_load_libgcc_insn_p (insn, ADDR_SPACE_FLASHX, false))"
   { gcc_unreachable(); }
   "&& 1"
-  [(clobber (const_int 0))]
+  [(scratch)]
   {
     // Split away the high part of the address.  GCC's register allocator
     // is not able to allocate segment registers and reload the resulting
     // expressions.  Notice that no address register can hold a PSImode.
 
+    addr_space_t as = MEM_ADDR_SPACE (operands[1]);
     rtx addr = XEXP (operands[1], 0);
     rtx hi8 = gen_reg_rtx (QImode);
     rtx reg_z = gen_rtx_REG (HImode, REG_Z);
@@ -606,29 +603,68 @@ (define_insn_and_split "xload8<mode>_A"
     emit_move_insn (reg_z, simplify_gen_subreg (HImode, addr, PSImode, 0));
     emit_move_insn (hi8, simplify_gen_subreg (QImode, addr, PSImode, 2));
 
-    rtx_insn *insn = emit_insn (gen_xload<mode>_8 (operands[0], hi8));
-    set_mem_addr_space (SET_SRC (single_set (insn)),
-                                 MEM_ADDR_SPACE (operands[1]));
+    rtx_insn *insn;
+    if (as == ADDR_SPACE_MEMX)
+      insn = emit_insn (gen_xmov<mode>_8 (operands[0], hi8));
+    else if (as == ADDR_SPACE_FLASHX)
+      insn = emit_insn (gen_fmov<mode> (operands[0], hi8));
+    else
+      gcc_unreachable ();
+
+    set_mem_addr_space (SET_SRC (single_set (insn)), as);
     DONE;
   })
 
-;; "xloadqi_A" "xloadqq_A" "xloaduqq_A"
-;; "xloadhi_A" "xloadhq_A" "xloaduhq_A" "xloadha_A" "xloaduha_A"
-;; "xloadsi_A" "xloadsq_A" "xloadusq_A" "xloadsa_A" "xloadusa_A"
-;; "xloadpsi_A"
-;; "xloadsf_A"
-(define_insn_and_split "xload<mode>_A"
+;; Move value from address space memx or flashx to a register
+;; These insns must be prior to respective generic move insn.
+
+;; "xmovqi_8"
+;; "xmovqq_8" "xmovuqq_8"
+(define_insn "xmov<mode>_8"
+  [(set (match_operand:MOVMODE 0 "register_operand"                   "=&r,r")
+        (mem:MOVMODE (lo_sum:PSI (match_operand:QI 1 "register_operand" "r,r")
+                                 (reg:HI REG_Z))))]
+  "<SIZE> == 1
+   && avr_load_libgcc_insn_p (insn, ADDR_SPACE_MEMX, false)"
+  {
+    return avr_out_xload (insn, operands, NULL);
+  }
+  [(set_attr "length" "4,4")
+   (set_attr "adjust_len" "*,xload")
+   (set_attr "isa" "lpmx,lpm")])
+
+;; Load a value from __flashx inline.
+(define_insn "fmov<mode>"
+  [(set (match_operand:MOVMODE 0 "register_operand"                    "=r")
+        (mem:MOVMODE (lo_sum:PSI (match_operand:QI 1 "register_operand" "r")
+                                 (reg:HI REG_Z))))
+   (clobber (reg:HI REG_Z))]
+  "avr_load_libgcc_insn_p (insn, ADDR_SPACE_FLASHX, false)"
+  {
+    return avr_out_fload (insn, operands, NULL);
+  }
+  [(set_attr "adjust_len" "fload")])
+
+
+;; Load a __memx or __flashx value per libgcc call.
+;; "fxloadqi_A" "fxloadqq_A" "fxloaduqq_A"
+;; "fxloadhi_A" "fxloadhq_A" "fxloaduhq_A" "fxloadha_A" "fxloaduha_A"
+;; "fxloadsi_A" "fxloadsq_A" "fxloadusq_A" "fxloadsa_A" "fxloadusa_A"
+;; "fxloadpsi_A"
+;; "fxloadsf_A"
+(define_insn_and_split "fxload<mode>_A"
   [(set (match_operand:MOVMODE 0 "register_operand" "=r")
         (match_operand:MOVMODE 1 "memory_operand"    "m"))
-   (clobber (reg:MOVMODE 22))
-   (clobber (reg:QI 21))
+   (clobber (reg:MOVMODE REG_22))
+   (clobber (reg:QI REG_21))
    (clobber (reg:HI REG_Z))]
   "can_create_pseudo_p()
-   && avr_mem_memx_p (operands[1])
-   && REG_P (XEXP (operands[1], 0))"
+   && REG_P (XEXP (operands[1], 0))
+   && (avr_load_libgcc_insn_p (insn, ADDR_SPACE_MEMX, true)
+       || avr_load_libgcc_insn_p (insn, ADDR_SPACE_FLASHX, true))"
   { gcc_unreachable(); }
   "&& 1"
-  [(clobber (const_int 0))]
+  [(scratch)]
   {
     rtx addr = XEXP (operands[1], 0);
     rtx reg_z = gen_rtx_REG (HImode, REG_Z);
@@ -637,65 +673,57 @@ (define_insn_and_split "xload<mode>_A"
 
     // Split the address to R21:Z
     emit_move_insn (reg_z, simplify_gen_subreg (HImode, addr, PSImode, 0));
-    emit_move_insn (gen_rtx_REG (QImode, 21), addr_hi8);
+    emit_move_insn (gen_rtx_REG (QImode, REG_21), addr_hi8);
 
     // Load with code from libgcc.
-    rtx_insn *insn = emit_insn (gen_xload_<mode>_libgcc ());
+    rtx_insn *insn = emit_insn (gen_fxload_<mode>_libgcc ());
     set_mem_addr_space (SET_SRC (single_set (insn)), as);
 
     // Move to destination.
-    emit_move_insn (operands[0], gen_rtx_REG (<MODE>mode, 22));
+    emit_move_insn (operands[0], gen_rtx_REG (<MODE>mode, REG_22));
 
     DONE;
   })
 
-;; Move value from address space memx to a register
-;; These insns must be prior to respective generic move insn.
-
-;; "xloadqi_8"
-;; "xloadqq_8" "xloaduqq_8"
-(define_insn "xload<mode>_8"
-  [(set (match_operand:ALL1 0 "register_operand"                   "=&r,r")
-        (mem:ALL1 (lo_sum:PSI (match_operand:QI 1 "register_operand" "r,r")
-                              (reg:HI REG_Z))))]
-  "!avr_xload_libgcc_p (<MODE>mode)"
-  {
-    return avr_out_xload (insn, operands, NULL);
-  }
-  [(set_attr "length" "4,4")
-   (set_attr "adjust_len" "*,xload")
-   (set_attr "isa" "lpmx,lpm")])
-
 ;; R21:Z : 24-bit source address
 ;; R22   : 1-4 byte output
 
-;; "xload_qi_libgcc" "xload_qq_libgcc" "xload_uqq_libgcc"
-;; "xload_hi_libgcc" "xload_hq_libgcc" "xload_uhq_libgcc" "xload_ha_libgcc" "xload_uha_libgcc"
-;; "xload_si_libgcc" "xload_sq_libgcc" "xload_usq_libgcc" "xload_sa_libgcc" "xload_usa_libgcc"
-;; "xload_sf_libgcc"
-;; "xload_psi_libgcc"
-(define_insn_and_split "xload_<mode>_libgcc"
-  [(set (reg:MOVMODE 22)
-        (mem:MOVMODE (lo_sum:PSI (reg:QI 21)
+;; "fxload_qi_libgcc" "fxload_qq_libgcc" "fxload_uqq_libgcc"
+;; "fxload_hi_libgcc" "fxload_hq_libgcc" "fxload_uhq_libgcc" "fxload_ha_libgcc" "xload_uha_libgcc"
+;; "fxload_si_libgcc" "fxload_sq_libgcc" "fxload_usq_libgcc" "fxload_sa_libgcc" "xload_usa_libgcc"
+;; "fxload_sf_libgcc"
+;; "fxload_psi_libgcc"
+(define_insn_and_split "fxload_<mode>_libgcc"
+  [(set (reg:MOVMODE REG_22)
+        (mem:MOVMODE (lo_sum:PSI (reg:QI REG_21)
                                  (reg:HI REG_Z))))
-   (clobber (reg:QI 21))
+   (clobber (reg:QI REG_21))
    (clobber (reg:HI REG_Z))]
-  "avr_xload_libgcc_p (<MODE>mode)"
+  "avr_load_libgcc_insn_p (insn, ADDR_SPACE_MEMX, true)
+    || avr_load_libgcc_insn_p (insn, ADDR_SPACE_FLASHX, true)"
   "#"
   "&& reload_completed"
-  [(parallel [(set (reg:MOVMODE 22)
-                   (mem:MOVMODE (lo_sum:PSI (reg:QI 21)
-                                            (reg:HI REG_Z))))
-              (clobber (reg:CC REG_CC))])])
+  [(parallel [(set (reg:MOVMODE REG_22)
+                   (match_dup 0))
+              (clobber (reg:CC REG_CC))])]
+  {
+    operands[0] = SET_SRC (single_set (curr_insn));
+  })
 
-(define_insn "*xload_<mode>_libgcc"
-  [(set (reg:MOVMODE 22)
-        (mem:MOVMODE (lo_sum:PSI (reg:QI 21)
+(define_insn "*fxload_<mode>_libgcc"
+  [(set (reg:MOVMODE REG_22)
+        (mem:MOVMODE (lo_sum:PSI (reg:QI REG_21)
                                  (reg:HI REG_Z))))
    (clobber (reg:CC REG_CC))]
-  "avr_xload_libgcc_p (<MODE>mode)
-   && reload_completed"
-  "%~call __xload_<SIZE>"
+  "reload_completed
+   && (avr_load_libgcc_insn_p (insn, ADDR_SPACE_MEMX, true)
+       || avr_load_libgcc_insn_p (insn, ADDR_SPACE_FLASHX, true))"
+  {
+    rtx src = SET_SRC (single_set (insn));
+    return avr_mem_memx_p (src)
+      ? "%~call __xload_<SIZE>"
+      : "%~call __fload_<SIZE>";
+  }
   [(set_attr "type" "xcall")])
 
 
@@ -740,7 +768,19 @@ (define_expand "mov<mode>"
         operands[1] = src = copy_to_mode_reg (<MODE>mode, src);
       }
 
-    if (avr_mem_memx_p (src))
+    // Let __flashx decay to __flash on devices <= 64 KiB.
+    if (avr_mem_flashx_p (src)
+        && ! AVR_HAVE_ELPM)
+      {
+        rtx addr = XEXP (src, 0);
+        addr = copy_to_mode_reg (Pmode, avr_word (addr, 0));
+        // replace_equiv_address() hickupps, so do it by hand.
+        operands[1] = src = gen_rtx_MEM (<MODE>mode, addr);
+        set_mem_addr_space (src, ADDR_SPACE_FLASH);
+      }
+
+    if (avr_mem_memx_p (src)
+        || avr_mem_flashx_p (src))
       {
         rtx addr = XEXP (src, 0);
 
@@ -751,10 +791,11 @@ (define_expand "mov<mode>"
           ? gen_reg_rtx (<MODE>mode)
           : dest;
 
-        if (!avr_xload_libgcc_p (<MODE>mode))
-          // No <mode> here because gen_xload8<mode>_A only iterates over ALL1.
-          // insn-emit does not depend on the mode, it's all about operands.
-          emit_insn (gen_xload8qi_A (dest2, src));
+        if (avr_load_libgcc_mem_p (src, ADDR_SPACE_MEMX, false)
+            || avr_load_libgcc_mem_p (src, ADDR_SPACE_FLASHX, false))
+          {
+            emit_insn (gen_fxmov<mode>_A (dest2, src));
+          }
         else
           {
             rtx reg_22 = gen_rtx_REG (<MODE>mode, REG_22);
@@ -762,7 +803,7 @@ (define_expand "mov<mode>"
                 || reg_overlap_mentioned_p (dest2, all_regs_rtx[REG_21]))
               dest2 = gen_reg_rtx (<MODE>mode);
 
-            emit_insn (gen_xload<mode>_A (dest2, src));
+            emit_insn (gen_fxload<mode>_A (dest2, src));
           }
 
         if (dest2 != dest)
@@ -774,7 +815,7 @@ (define_expand "mov<mode>"
     if (avr_load_libgcc_p (src))
       {
         // For the small devices, do loads per libgcc call.
-        emit_insn (gen_load<mode>_libgcc (dest, src));
+        emit_insn (gen_gen_load<mode>_libgcc (dest, src));
         DONE;
       }
   })
@@ -1297,7 +1338,7 @@ (define_insn "*cpymem_<mode>"
   [(set_attr "adjust_len" "cpymem")])
 
 
-;; $0    : Address Space
+;; $0    : 24-bit address space
 ;; $1    : RAMPZ RAM address
 ;; R24   : #bytes and loop register
 ;; R23:Z : 24-bit source address
@@ -1308,7 +1349,9 @@ (define_insn "*cpymem_<mode>"
 
 (define_insn_and_split "cpymemx_<mode>"
   [(set (mem:BLK (reg:HI REG_X))
-        (mem:BLK (lo_sum:PSI (reg:QI 23)
+        ;; Spell out the address.  IRA may try to spill
+        ;; a hard reg when operands were used.
+        (mem:BLK (lo_sum:PSI (reg:QI REG_23)
                              (reg:HI REG_Z))))
    (unspec [(match_operand:QI 0 "const_int_operand" "n")]
            UNSPEC_CPYMEM)
@@ -1323,8 +1366,7 @@ (define_insn_and_split "cpymemx_<mode>"
   "#"
   "&& reload_completed"
   [(parallel [(set (mem:BLK (reg:HI REG_X))
-                   (mem:BLK (lo_sum:PSI (reg:QI 23)
-                                        (reg:HI REG_Z))))
+                   (match_dup 2))
               (unspec [(match_dup 0)]
                       UNSPEC_CPYMEM)
               (use (reg:QIHI 24))
@@ -1334,11 +1376,15 @@ (define_insn_and_split "cpymemx_<mode>"
               (clobber (reg:HI 24))
               (clobber (reg:QI 23))
               (clobber (mem:QI (match_dup 1)))
-              (clobber (reg:CC REG_CC))])])
+              (clobber (reg:CC REG_CC))])]
+  {
+    rtx xset = XVECEXP (PATTERN (curr_insn), 0, 0);
+    operands[2] = SET_SRC (xset);
+  })
 
 (define_insn "*cpymemx_<mode>"
   [(set (mem:BLK (reg:HI REG_X))
-        (mem:BLK (lo_sum:PSI (reg:QI 23)
+        (mem:BLK (lo_sum:PSI (reg:QI REG_23)
                              (reg:HI REG_Z))))
    (unspec [(match_operand:QI 0 "const_int_operand" "n")]
            UNSPEC_CPYMEM)
@@ -1351,7 +1397,12 @@ (define_insn "*cpymemx_<mode>"
    (clobber (mem:QI (match_operand:QI 1 "io_address_operand" "n")))
    (clobber (reg:CC REG_CC))]
   "reload_completed"
-  "%~call __movmemx_<mode>"
+  {
+    addr_space_t as = (addr_space_t) INTVAL (operands[0]);
+    return as == ADDR_SPACE_MEMX
+      ? "%~call __movmemx_<mode>"
+      : "%~call __movmemf_<mode>";
+  }
   [(set_attr "type" "xcall")])
 
 
@@ -5576,6 +5627,31 @@ (define_insn "*ashlpsi3"
   [(set_attr "isa" "*,*,*,3op,*")
    (set_attr "adjust_len" "ashlpsi")])
 
+;; Seen in PSI loads from __flashx tables.
+(define_insn_and_split "*ashlqi.1.zextpsi_split"
+  [(set (match_operand:PSI 0 "register_operand"  "=r")
+        (zero_extend:PSI
+         (ashift:HI (zero_extend:HI (match_operand:QI 1 "register_operand" "0"))
+                    (const_int 1))))]
+  ""
+  "#"
+  "&& reload_completed"
+  [(parallel [(set (match_dup 2)
+                   (const_int 0))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (match_dup 3)
+                   (const_int 0))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (match_dup 4)
+                   (ashift:HI (match_dup 4)
+                              (const_int 1)))
+              (clobber (reg:CC REG_CC))])]
+  {
+    operands[2] = avr_byte (operands[0], 2);
+    operands[3] = avr_byte (operands[0], 1);
+    operands[4] = avr_word (operands[0], 0);
+  })
+
 ;; >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >>
 ;; arithmetic shift right
 
diff --git a/gcc/config/avr/predicates.md b/gcc/config/avr/predicates.md
index a28c63a6478..8253fb9f62c 100644
--- a/gcc/config/avr/predicates.md
+++ b/gcc/config/avr/predicates.md
@@ -89,6 +89,7 @@ (define_predicate "nop_general_operand"
 (define_predicate "nox_general_operand"
   (and (match_operand 0 "general_operand")
        (not (match_test "avr_load_libgcc_p (op)"))
+       (not (match_test "avr_mem_flashx_p (op)"))
        (not (match_test "avr_mem_memx_p (op)"))))
 
 ;; Return 1 if OP is the zero constant for MODE.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 336361e0cfd..5297239a8ff 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -1525,6 +1525,12 @@ address space @code{__flash@var{N}}.
 The compiler sets the @code{RAMPZ} segment register appropriately 
 before reading data by means of the @code{ELPM} instruction.
 
+@cindex @code{__flashx} AVR Named Address Spaces
+@item __flashx
+
+This is a 24-bit address space locating data in section
+@code{.progmemx.data}.
+
 @cindex @code{__memx} AVR Named Address Spaces
 @item __memx
 This is a 24-bit address space that linearizes flash and RAM:
@@ -1604,9 +1610,9 @@ together with attribute @code{progmem}.
 @item
 Reading across the 64@tie{}KiB section boundary of
 the @code{__flash} or @code{__flash@var{N}} address spaces
-shows undefined behavior. The only address space that
-supports reading across the 64@tie{}KiB flash segment boundaries is
-@code{__memx}.
+shows undefined behavior. The only address spaces that
+supports reading across the 64@tie{}KiB flash segment boundaries are
+@code{__memx} and @code{__flashx}.
 
 @item
 If you use one of the @code{__flash@var{N}} address spaces
diff --git a/gcc/testsuite/gcc.target/avr/torture/addr-space-1-fx.c b/gcc/testsuite/gcc.target/avr/torture/addr-space-1-fx.c
new file mode 100644
index 00000000000..f15fac0e894
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/torture/addr-space-1-fx.c
@@ -0,0 +1,6 @@
+/* { dg-options "-std=gnu99" } */
+/* { dg-do run { target { ! avr_tiny } } } */
+
+#define __as __flashx
+
+#include "addr-space-1.h"
diff --git a/gcc/testsuite/gcc.target/avr/torture/addr-space-1.h b/gcc/testsuite/gcc.target/avr/torture/addr-space-1.h
index 322a5b8b3b6..8b1c12c2ddc 100644
--- a/gcc/testsuite/gcc.target/avr/torture/addr-space-1.h
+++ b/gcc/testsuite/gcc.target/avr/torture/addr-space-1.h
@@ -31,8 +31,21 @@ const __as volatile a_t V =
 a_t A2;
 volatile a_t V2;
 
+#ifdef __AVR_HAVE_ELPM__
+void eat_flash (void)
+{
+  __asm (".space 0x10000");
+}
+__attribute__((__used__))
+void (*pfun) (void);
+#endif
+
 int main (void)
 {
+#ifdef __AVR_HAVE_ELPM__
+  pfun = eat_flash;
+#endif
+
   if (A.i1 != 12
       || A.i1 != V.i1 -1)
     abort();
diff --git a/gcc/testsuite/gcc.target/avr/torture/addr-space-2-fx.c b/gcc/testsuite/gcc.target/avr/torture/addr-space-2-fx.c
new file mode 100644
index 00000000000..65220e9552f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/torture/addr-space-2-fx.c
@@ -0,0 +1,9 @@
+/* { dg-options "-std=gnu99 -Wa,--no-warn" } */
+/* { dg-do run { target { ! avr_tiny } } } */
+
+/* --no-warn because: "assembling 24-bit address needs binutils extension"
+   see binutils PR13503.  */
+
+#define __as __flashx
+
+#include "addr-space-2.h"
diff --git a/gcc/testsuite/gcc.target/avr/torture/addr-space-2.h b/gcc/testsuite/gcc.target/avr/torture/addr-space-2.h
index c95a1631ab0..7146f631650 100644
--- a/gcc/testsuite/gcc.target/avr/torture/addr-space-2.h
+++ b/gcc/testsuite/gcc.target/avr/torture/addr-space-2.h
@@ -90,8 +90,21 @@ test3 (const __as tree *pt)
     abort();
 }
 
+#ifdef __AVR_HAVE_ELPM__
+void eat_flash (void)
+{
+  __asm (".space 0x10000");
+}
+__attribute__((__used__))
+void (*pfun) (void);
+#endif
+
 int main (void)
 {
+#ifdef __AVR_HAVE_ELPM__
+  pfun = eat_flash;
+#endif
+
   const __as tree *t = &abcd;
   test1();
   test2 (&abcd);
diff --git a/libgcc/config/avr/lib1funcs.S b/libgcc/config/avr/lib1funcs.S
index d48b04747da..d5f4c721779 100644
--- a/libgcc/config/avr/lib1funcs.S
+++ b/libgcc/config/avr/lib1funcs.S
@@ -101,6 +101,19 @@
 #define XIJMP  ijmp
 #endif
 
+;;; [R]JMP to label \labl when \reg is positive (\reg.7 = 0).
+;;; Otherwise, fallthrough.
+.macro  .branch_plus, reg, labl
+#ifdef __AVR_ERRATA_SKIP_JMP_CALL__
+    tst     \reg
+    brmi    .L..\@
+#else
+    sbrs    \reg, 7
+#endif /* skip erratum */
+    XJMP    \labl
+.L..\@:
+.endm ; .branch_plus
+
 ;; Prologue stuff
 
 .macro do_prologue_saves n_pushed n_frame=0
@@ -2596,34 +2609,6 @@ DEFUN __load_4
 
 #define HHI8  21
 
-.macro  .xload dest, n
-#if defined (__AVR_HAVE_ELPMX__)
-    elpm    \dest, Z+
-#elif defined (__AVR_HAVE_ELPM__)
-    elpm
-    mov     \dest, r0
-.if \dest != D0+\n-1
-    adiw    r30, 1
-    adc     HHI8, __zero_reg__
-    out     __RAMPZ__, HHI8
-.endif
-#elif defined (__AVR_HAVE_LPMX__)
-    lpm     \dest, Z+
-#else
-    lpm
-    mov     \dest, r0
-.if \dest != D0+\n-1
-    adiw    r30, 1
-.endif
-#endif
-#if defined (__AVR_HAVE_ELPM__) && defined (__AVR_HAVE_RAMPD__)
-.if \dest == D0+\n-1
-    ;; Reset RAMPZ to 0 so that EBI devices don't read garbage from RAM
-    out     __RAMPZ__, __zero_reg__
-.endif
-#endif
-.endm ; .xload
-
 #if defined (L_xload_1)
 DEFUN __xload_1
 #if defined (__AVR_HAVE_LPMX__) && !defined (__AVR_HAVE_ELPM__)
@@ -2633,14 +2618,8 @@ DEFUN __xload_1
     lpm     D0, Z
     ret
 #else
-    sbrc    HHI8, 7
-    rjmp    1f
-#if defined (__AVR_HAVE_ELPM__)
-    out     __RAMPZ__, HHI8
-#endif /* __AVR_HAVE_ELPM__ */
-    .xload  D0, 1
-    ret
-1:  ld      D0, Z
+    .branch_plus HHI8 __fload_1
+    ld      D0, Z
     ret
 #endif /* LPMx && ! ELPM */
 ENDF __xload_1
@@ -2648,15 +2627,8 @@ DEFUN __xload_1
 
 #if defined (L_xload_2)
 DEFUN __xload_2
-    sbrc    HHI8, 7
-    rjmp    1f
-#if defined (__AVR_HAVE_ELPM__)
-    out     __RAMPZ__, HHI8
-#endif /* __AVR_HAVE_ELPM__ */
-    .xload  D0, 2
-    .xload  D1, 2
-    ret
-1:  ld      D0, Z+
+    .branch_plus HHI8 __fload_2
+    ld      D0, Z+
     ld      D1, Z+
     ret
 ENDF __xload_2
@@ -2664,16 +2636,8 @@ DEFUN __xload_2
 
 #if defined (L_xload_3)
 DEFUN __xload_3
-    sbrc    HHI8, 7
-    rjmp    1f
-#if defined (__AVR_HAVE_ELPM__)
-    out     __RAMPZ__, HHI8
-#endif /* __AVR_HAVE_ELPM__ */
-    .xload  D0, 3
-    .xload  D1, 3
-    .xload  D2, 3
-    ret
-1:  ld      D0, Z+
+    .branch_plus HHI8 __fload_3
+    ld      D0, Z+
     ld      D1, Z+
     ld      D2, Z+
     ret
@@ -2682,17 +2646,8 @@ DEFUN __xload_3
 
 #if defined (L_xload_4)
 DEFUN __xload_4
-    sbrc    HHI8, 7
-    rjmp    1f
-#if defined (__AVR_HAVE_ELPM__)
-    out     __RAMPZ__, HHI8
-#endif /* __AVR_HAVE_ELPM__ */
-    .xload  D0, 4
-    .xload  D1, 4
-    .xload  D2, 4
-    .xload  D3, 4
-    ret
-1:  ld      D0, Z+
+    .branch_plus HHI8 __fload_4
+    ld      D0, Z+
     ld      D1, Z+
     ld      D2, Z+
     ld      D3, Z+
@@ -2705,7 +2660,103 @@ DEFUN __xload_4
 
 #if !defined (__AVR_TINY__)
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; memcopy from Address Space __pgmx to RAM
+;; Loading n bytes from Flash;  n = 1,2,3,4
+;; R22... = Flash[R21:Z]
+;; Clobbers: __tmp_reg__, R21, R30, R31
+
+#if (defined (L_fload_1)            \
+     || defined (L_fload_2)         \
+     || defined (L_fload_3)         \
+     || defined (L_fload_4))
+
+;; Destination
+#define D0  22
+#define D1  D0+1
+#define D2  D0+2
+#define D3  D0+3
+
+;; Register containing bits 16+ of the address
+
+#define HHI8  21
+
+.macro  .fload dest, n
+#if defined (__AVR_HAVE_ELPM__)
+.if \dest == D0
+    out     __RAMPZ__, HHI8
+.endif
+#endif /* __AVR_HAVE_ELPM__ */
+#if defined (__AVR_HAVE_ELPMX__)
+    elpm    \dest, Z+
+#elif defined (__AVR_HAVE_ELPM__)
+    elpm
+    mov     \dest, r0
+.if \dest != D0+\n-1
+    adiw    r30, 1
+    adc     HHI8, __zero_reg__
+    out     __RAMPZ__, HHI8
+.endif
+#elif defined (__AVR_HAVE_LPMX__)
+    lpm     \dest, Z+
+#else
+    lpm
+    mov     \dest, r0
+.if \dest != D0+\n-1
+    adiw    r30, 1
+.endif
+#endif
+#if defined (__AVR_HAVE_ELPM__) && defined (__AVR_HAVE_RAMPD__)
+.if \dest == D0+\n-1
+    ;; Reset RAMPZ to 0 so that EBI devices don't read garbage from RAM
+    out     __RAMPZ__, __zero_reg__
+.endif
+#endif
+.endm ; .fload
+
+#if defined (L_fload_1)
+DEFUN __fload_1
+#if defined (__AVR_HAVE_LPMX__) && !defined (__AVR_HAVE_ELPM__)
+    lpm     D0, Z
+    ret
+#else
+    .fload  D0, 1
+    ret
+#endif /* LPMx && ! ELPM */
+ENDF __fload_1
+#endif /* L_fload_1 */
+
+#if defined (L_fload_2)
+DEFUN __fload_2
+    .fload  D0, 2
+    .fload  D1, 2
+    ret
+ENDF __fload_2
+#endif /* L_fload_2 */
+
+#if defined (L_fload_3)
+DEFUN __fload_3
+    .fload  D0, 3
+    .fload  D1, 3
+    .fload  D2, 3
+    ret
+ENDF __fload_3
+#endif /* L_fload_3 */
+
+#if defined (L_fload_4)
+DEFUN __fload_4
+    .fload  D0, 4
+    .fload  D1, 4
+    .fload  D2, 4
+    .fload  D3, 4
+    ret
+ENDF __fload_4
+#endif /* L_fload_4 */
+
+#endif /* L_fload_{1|2|3|4} */
+#endif /* if !defined (__AVR_TINY__) */
+
+#if !defined (__AVR_TINY__)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; memcopy from Address Space __memx to RAM
 ;; R23:Z = Source Address
 ;; X     = Destination Address
 ;; Clobbers: __tmp_reg__, R23, R24, R25, X, Z
@@ -2716,7 +2767,7 @@ DEFUN __xload_4
 #define LOOP  24
 
 DEFUN __movmemx_qi
-    ;; #Bytes to copy fity in 8 Bits (1..255)
+    ;; #Bytes to copy fits in 8 Bits (1..255)
     ;; Zero-extend Loop Counter
     clr     LOOP+1
     ;; FALLTHRU
@@ -2724,9 +2775,41 @@ DEFUN __movmemx_qi
 
 DEFUN __movmemx_hi
 
-;; Read from where?
-    sbrc    HHI8, 7
-    rjmp    1f
+    .branch_plus    HHI8 __movmemf_hi
+
+    ;; Read 1 Byte from RAM...
+1:  ld      r0, Z+
+    ;; and store that Byte to RAM Destination
+    st      X+, r0
+    sbiw    LOOP, 1
+    brne    1b
+    ret
+ENDF __movmemx_hi
+
+#undef HHI8
+#undef LOOP
+
+#endif /* L_movmemx */
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; memcopy from Address Space __flashx to RAM
+;; R23:Z = Source Address
+;; X     = Destination Address
+;; Clobbers: __tmp_reg__, R23, R24, R25, X, Z
+
+#if defined (L_movmemf)
+
+#define HHI8  23
+#define LOOP  24
+
+DEFUN __movmemf_qi
+    ;; #Bytes to copy fits in 8 Bits (1..255)
+    ;; Zero-extend Loop Counter
+    clr     LOOP+1
+    ;; FALLTHRU
+ENDF __movmemf_qi
+
+DEFUN __movmemf_hi
 
 ;; Read from Flash
 
@@ -2759,22 +2842,12 @@ DEFUN __movmemx_hi
     out	__RAMPZ__, __zero_reg__
 #endif /* ELPM && RAMPD */
     ret
-
-;; Read from RAM
-
-1:  ;; Read 1 Byte from RAM...
-    ld      r0, Z+
-    ;; and store that Byte to RAM Destination
-    st      X+, r0
-    sbiw    LOOP, 1
-    brne    1b
-    ret
-ENDF __movmemx_hi
+ENDF __movmemf_hi
 
 #undef HHI8
 #undef LOOP
 
-#endif /* L_movmemx */
+#endif /* L_movmemf */
 #endif /* !defined (__AVR_TINY__) */ 
 
 
diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr
index 971a092aceb..e9fdb98d776 100644
--- a/libgcc/config/avr/t-avr
+++ b/libgcc/config/avr/t-avr
@@ -36,13 +36,13 @@ LIB1ASMFUNCS = \
 	_fmul _fmuls _fmulsu
 
 # The below functions either use registers that are not present
-# in tiny core, or use a different register conventions (don't save
+# in tiny core, or use a different register convention (don't save
 # callee saved regs, for example)
 # _mulhisi3 and variations - clobber R18, R19
 # All *di funcs - use regs < R16 or expect args in regs < R20
 # _prologue and _epilogue save registers < R16
-# _load ad _xload variations - expect lpm and elpm support
-# _movmemx - expects elpm/lpm
+# _load, __fload and _xload variations - expect lpm and elpm support
+# _movmemx and _movmemf - expect elpm/lpm
 
 ifneq ($(MULTIFLAGS),-mmcu=avrtiny)
 LIB1ASMFUNCS += \
@@ -61,7 +61,8 @@ LIB1ASMFUNCS += \
 	_epilogue \
 	_load_3 _load_4 \
 	_xload_1 _xload_2 _xload_3 _xload_4 \
-	_movmemx \
+	_fload_1 _fload_2 _fload_3 _fload_4 \
+	_movmemx _movmemf \
 	_clzdi2 \
 	_paritydi2 \
 	_popcountdi2 \

Reply via email to