AVR: target/118001 - 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 asm 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.

	PR target/118001
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.
This commit is contained in:
Georg-Johann Lay 2024-12-11 13:28:47 +01:00
parent d46c7f313b
commit f8a602ce53
13 changed files with 575 additions and 204 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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" },
};
@ -646,7 +647,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)
@ -661,6 +662,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
@ -671,8 +688,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)
@ -682,6 +699,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. */
@ -3204,7 +3232,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)
@ -3216,6 +3245,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. */
@ -3677,7 +3766,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)
@ -3707,6 +3798,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.
@ -10726,6 +10864,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;
@ -11151,7 +11290,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))
@ -14147,6 +14287,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 ());
@ -14162,7 +14303,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)
@ -14210,19 +14351,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);
@ -14239,14 +14381,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);
@ -14259,8 +14403,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);
@ -14286,6 +14429,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
@ -14326,8 +14481,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;
@ -14511,9 +14665,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)
{
gcc_assert (as == ADDR_SPACE_MEMX);
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 || as == ADDR_SPACE_FLASHX);
loop_mode = (count < 0x100) ? QImode : HImode;
loop_reg = gen_rtx_REG (loop_mode, 24);
@ -14561,7 +14724,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. */
@ -14576,7 +14739,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));
}

View file

@ -53,6 +53,7 @@ enum
ADDR_SPACE_FLASH3,
ADDR_SPACE_FLASH4,
ADDR_SPACE_FLASH5,
ADDR_SPACE_FLASHX,
ADDR_SPACE_MEMX,
/* Sentinel */
ADDR_SPACE_COUNT

View file

@ -164,7 +164,7 @@
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,17 +532,13 @@
;;========================================================================
;; 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)
(match_operand:MOVMODE 1 "memory_operand" ""))
(set (match_operand:MOVMODE 0 "register_operand" "")
(match_operand:MOVMODE 1 "memory_operand"))
(set (match_operand:MOVMODE 0 "register_operand")
(reg:MOVMODE 22))]
"avr_load_libgcc_p (operands[1])"
{
@ -581,24 +577,25 @@
[(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 @@
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 @@
// 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")])
@ -707,8 +735,8 @@
;; "movsf"
;; "movpsi"
(define_expand "mov<mode>"
[(set (match_operand:MOVMODE 0 "nonimmediate_operand" "")
(match_operand:MOVMODE 1 "general_operand" ""))]
[(set (match_operand:MOVMODE 0 "nonimmediate_operand")
(match_operand:MOVMODE 1 "general_operand"))]
""
{
rtx dest = operands[0];
@ -740,7 +768,19 @@
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 @@
? 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 @@
|| 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 @@
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 @@
[(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_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 @@
"#"
"&& 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 @@
(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 @@
(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 @@
[(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

View file

@ -89,6 +89,7 @@
(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.

View file

@ -1525,6 +1525,14 @@ 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 flash address space locating data in section
@code{.progmemx.data}.
The compiler sets the @code{RAMPZ} segment register appropriately
before reading data by means of the @code{ELPM} instruction.
@cindex @code{__memx} AVR Named Address Spaces
@item __memx
This is a 24-bit address space that linearizes flash and RAM:
@ -1604,9 +1612,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}.
is not supported. The only address spaces that
support 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

View file

@ -0,0 +1,6 @@
/* { dg-options "-std=gnu99" } */
/* { dg-do run { target { ! avr_tiny } } } */
#define __as __flashx
#include "addr-space-1.h"

View file

@ -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();

View file

@ -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"

View file

@ -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);

View file

@ -101,6 +101,20 @@ see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
#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__
;; Some cores have a problem skipping 2-word instructions
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,7 +2610,82 @@ ENDF __load_4
#define HHI8 21
.macro .xload dest, n
#if defined (L_xload_1)
DEFUN __xload_1
#if defined (__AVR_HAVE_LPMX__) && !defined (__AVR_HAVE_ELPM__)
sbrc HHI8, 7
ld D0, Z
sbrs HHI8, 7
lpm D0, Z
ret
#else
.branch_plus HHI8, __fload_1
ld D0, Z
ret
#endif /* LPMx && ! ELPM */
ENDF __xload_1
#endif /* L_xload_1 */
#if defined (L_xload_2)
DEFUN __xload_2
.branch_plus HHI8, __fload_2
ld D0, Z+
ld D1, Z+
ret
ENDF __xload_2
#endif /* L_xload_2 */
#if defined (L_xload_3)
DEFUN __xload_3
.branch_plus HHI8, __fload_3
ld D0, Z+
ld D1, Z+
ld D2, Z+
ret
ENDF __xload_3
#endif /* L_xload_3 */
#if defined (L_xload_4)
DEFUN __xload_4
.branch_plus HHI8, __fload_4
ld D0, Z+
ld D1, Z+
ld D2, Z+
ld D3, Z+
ret
ENDF __xload_4
#endif /* L_xload_4 */
#endif /* L_xload_{1|2|3|4} */
#endif /* if !defined (__AVR_TINY__) */
#if !defined (__AVR_TINY__)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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__)
@ -2622,90 +2711,53 @@ ENDF __load_4
out __RAMPZ__, __zero_reg__
.endif
#endif
.endm ; .xload
.endm ; .fload
#if defined (L_xload_1)
DEFUN __xload_1
#if defined (L_fload_1)
DEFUN __fload_1
#if defined (__AVR_HAVE_LPMX__) && !defined (__AVR_HAVE_ELPM__)
sbrc HHI8, 7
ld D0, Z
sbrs HHI8, 7
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
.fload D0, 1
ret
#endif /* LPMx && ! ELPM */
ENDF __xload_1
#endif /* L_xload_1 */
ENDF __fload_1
#endif /* L_fload_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
#if defined (L_fload_2)
DEFUN __fload_2
.fload D0, 2
.fload D1, 2
ret
1: ld D0, Z+
ld D1, Z+
ret
ENDF __xload_2
#endif /* L_xload_2 */
ENDF __fload_2
#endif /* L_fload_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
#if defined (L_fload_3)
DEFUN __fload_3
.fload D0, 3
.fload D1, 3
.fload D2, 3
ret
1: ld D0, Z+
ld D1, Z+
ld D2, Z+
ret
ENDF __xload_3
#endif /* L_xload_3 */
ENDF __fload_3
#endif /* L_fload_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
#if defined (L_fload_4)
DEFUN __fload_4
.fload D0, 4
.fload D1, 4
.fload D2, 4
.fload D3, 4
ret
1: ld D0, Z+
ld D1, Z+
ld D2, Z+
ld D3, Z+
ret
ENDF __xload_4
#endif /* L_xload_4 */
ENDF __fload_4
#endif /* L_fload_4 */
#endif /* L_xload_{1|2|3|4} */
#endif /* L_fload_{1|2|3|4} */
#endif /* if !defined (__AVR_TINY__) */
#if !defined (__AVR_TINY__)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; memcopy from Address Space __pgmx to RAM
;; memcopy from Address Space __memx to RAM
;; R23:Z = Source Address
;; X = Destination Address
;; Clobbers: __tmp_reg__, R23, R24, R25, X, Z
@ -2716,7 +2768,7 @@ ENDF __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 +2776,41 @@ ENDF __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 +2843,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__) */

View file

@ -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 \