blob: a2b0c830568713ce40f4674ec0ebab6fb61eb239 [file] [log] [blame]
/* Convert tree expression to rtl instructions, for GNU compiler.
Copyright (C) 1988, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
2000, 2001, 2002, 2003 Free Software Foundation, Inc.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING. If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#include "config.h"
#include "system.h"
#include "machmode.h"
#include "real.h"
#include "rtl.h"
#include "tree.h"
#include "flags.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "except.h"
#include "function.h"
#include "insn-config.h"
#include "insn-attr.h"
/* Include expr.h after insn-config.h so we get HAVE_conditional_move. */
#include "expr.h"
#include "optabs.h"
#include "libfuncs.h"
#include "recog.h"
#include "reload.h"
#include "output.h"
#include "typeclass.h"
#include "toplev.h"
#include "ggc.h"
#include "langhooks.h"
#include "intl.h"
#include "tm_p.h"
/* Decide whether a function's arguments should be processed
from first to last or from last to first.
They should if the stack and args grow in opposite directions, but
only if we have push insns. */
#ifdef PUSH_ROUNDING
#ifndef PUSH_ARGS_REVERSED
#if defined (STACK_GROWS_DOWNWARD) != defined (ARGS_GROW_DOWNWARD)
#define PUSH_ARGS_REVERSED /* If it's last to first. */
#endif
#endif
#endif
#ifndef STACK_PUSH_CODE
#ifdef STACK_GROWS_DOWNWARD
#define STACK_PUSH_CODE PRE_DEC
#else
#define STACK_PUSH_CODE PRE_INC
#endif
#endif
/* Assume that case vectors are not pc-relative. */
#ifndef CASE_VECTOR_PC_RELATIVE
#define CASE_VECTOR_PC_RELATIVE 0
#endif
/* Convert defined/undefined to boolean. */
#ifdef TARGET_MEM_FUNCTIONS
#undef TARGET_MEM_FUNCTIONS
#define TARGET_MEM_FUNCTIONS 1
#else
#define TARGET_MEM_FUNCTIONS 0
#endif
/* If this is nonzero, we do not bother generating VOLATILE
around volatile memory references, and we are willing to
output indirect addresses. If cse is to follow, we reject
indirect addresses so a useful potential cse is generated;
if it is used only once, instruction combination will produce
the same indirect address eventually. */
int cse_not_expected;
/* Chain of pending expressions for PLACEHOLDER_EXPR to replace. */
static tree placeholder_list = 0;
/* This structure is used by move_by_pieces to describe the move to
be performed. */
struct move_by_pieces
{
rtx to;
rtx to_addr;
int autinc_to;
int explicit_inc_to;
rtx from;
rtx from_addr;
int autinc_from;
int explicit_inc_from;
unsigned HOST_WIDE_INT len;
HOST_WIDE_INT offset;
int reverse;
};
/* This structure is used by store_by_pieces to describe the clear to
be performed. */
struct store_by_pieces
{
rtx to;
rtx to_addr;
int autinc_to;
int explicit_inc_to;
unsigned HOST_WIDE_INT len;
HOST_WIDE_INT offset;
rtx (*constfun) PARAMS ((PTR, HOST_WIDE_INT, enum machine_mode));
PTR constfundata;
int reverse;
};
static rtx enqueue_insn PARAMS ((rtx, rtx));
static rtx mark_queue PARAMS ((void));
static void emit_insns_enqueued_after_mark PARAMS ((rtx));
static unsigned HOST_WIDE_INT move_by_pieces_ninsns
PARAMS ((unsigned HOST_WIDE_INT,
unsigned int));
static void move_by_pieces_1 PARAMS ((rtx (*) (rtx, ...), enum machine_mode,
struct move_by_pieces *));
static bool block_move_libcall_safe_for_call_parm PARAMS ((void));
static bool emit_block_move_via_movstr PARAMS ((rtx, rtx, rtx, unsigned));
static rtx emit_block_move_via_libcall PARAMS ((rtx, rtx, rtx));
static tree emit_block_move_libcall_fn PARAMS ((int));
static void emit_block_move_via_loop PARAMS ((rtx, rtx, rtx, unsigned));
static rtx clear_by_pieces_1 PARAMS ((PTR, HOST_WIDE_INT,
enum machine_mode));
static void clear_by_pieces PARAMS ((rtx, unsigned HOST_WIDE_INT,
unsigned int));
static void store_by_pieces_1 PARAMS ((struct store_by_pieces *,
unsigned int));
static void store_by_pieces_2 PARAMS ((rtx (*) (rtx, ...),
enum machine_mode,
struct store_by_pieces *));
static bool clear_storage_via_clrstr PARAMS ((rtx, rtx, unsigned));
static rtx clear_storage_via_libcall PARAMS ((rtx, rtx));
static tree clear_storage_libcall_fn PARAMS ((int));
static rtx compress_float_constant PARAMS ((rtx, rtx));
static rtx get_subtarget PARAMS ((rtx));
static int is_zeros_p PARAMS ((tree));
static int mostly_zeros_p PARAMS ((tree));
static void store_constructor_field PARAMS ((rtx, unsigned HOST_WIDE_INT,
HOST_WIDE_INT, enum machine_mode,
tree, tree, int, int));
static void store_constructor PARAMS ((tree, rtx, int, HOST_WIDE_INT));
static rtx store_field PARAMS ((rtx, HOST_WIDE_INT,
HOST_WIDE_INT, enum machine_mode,
tree, enum machine_mode, int, tree,
int));
static rtx var_rtx PARAMS ((tree));
static HOST_WIDE_INT highest_pow2_factor PARAMS ((tree));
static HOST_WIDE_INT highest_pow2_factor_for_type PARAMS ((tree, tree));
static int is_aligning_offset PARAMS ((tree, tree));
static rtx expand_increment PARAMS ((tree, int, int));
static void do_jump_by_parts_greater PARAMS ((tree, int, rtx, rtx));
static void do_jump_by_parts_equality PARAMS ((tree, rtx, rtx));
static void do_compare_and_jump PARAMS ((tree, enum rtx_code, enum rtx_code,
rtx, rtx));
static rtx do_store_flag PARAMS ((tree, rtx, enum machine_mode, int));
#ifdef PUSH_ROUNDING
static void emit_single_push_insn PARAMS ((enum machine_mode, rtx, tree));
#endif
static void do_tablejump PARAMS ((rtx, enum machine_mode, rtx, rtx, rtx));
static rtx const_vector_from_tree PARAMS ((tree));
/* Record for each mode whether we can move a register directly to or
from an object of that mode in memory. If we can't, we won't try
to use that mode directly when accessing a field of that mode. */
static char direct_load[NUM_MACHINE_MODES];
static char direct_store[NUM_MACHINE_MODES];
/* Record for each mode whether we can float-extend from memory. */
static bool float_extend_from_mem[NUM_MACHINE_MODES][NUM_MACHINE_MODES];
/* If a memory-to-memory move would take MOVE_RATIO or more simple
move-instruction sequences, we will do a movstr or libcall instead. */
#ifndef MOVE_RATIO
#if defined (HAVE_movstrqi) || defined (HAVE_movstrhi) || defined (HAVE_movstrsi) || defined (HAVE_movstrdi) || defined (HAVE_movstrti)
#define MOVE_RATIO 2
#else
/* If we are optimizing for space (-Os), cut down the default move ratio. */
#define MOVE_RATIO (optimize_size ? 3 : 15)
#endif
#endif
/* This macro is used to determine whether move_by_pieces should be called
to perform a structure copy. */
#ifndef MOVE_BY_PIECES_P
#define MOVE_BY_PIECES_P(SIZE, ALIGN) \
(move_by_pieces_ninsns (SIZE, ALIGN) < (unsigned int) MOVE_RATIO)
#endif
/* If a clear memory operation would take CLEAR_RATIO or more simple
move-instruction sequences, we will do a clrstr or libcall instead. */
#ifndef CLEAR_RATIO
#if defined (HAVE_clrstrqi) || defined (HAVE_clrstrhi) || defined (HAVE_clrstrsi) || defined (HAVE_clrstrdi) || defined (HAVE_clrstrti)
#define CLEAR_RATIO 2
#else
/* If we are optimizing for space, cut down the default clear ratio. */
#define CLEAR_RATIO (optimize_size ? 3 : 15)
#endif
#endif
/* This macro is used to determine whether clear_by_pieces should be
called to clear storage. */
#ifndef CLEAR_BY_PIECES_P
#define CLEAR_BY_PIECES_P(SIZE, ALIGN) \
(move_by_pieces_ninsns (SIZE, ALIGN) < (unsigned int) CLEAR_RATIO)
#endif
/* This array records the insn_code of insns to perform block moves. */
enum insn_code movstr_optab[NUM_MACHINE_MODES];
/* This array records the insn_code of insns to perform block clears. */
enum insn_code clrstr_optab[NUM_MACHINE_MODES];
/* SLOW_UNALIGNED_ACCESS is nonzero if unaligned accesses are very slow. */
#ifndef SLOW_UNALIGNED_ACCESS
#define SLOW_UNALIGNED_ACCESS(MODE, ALIGN) STRICT_ALIGNMENT
#endif
/* This is run once per compilation to set up which modes can be used
directly in memory and to initialize the block move optab. */
void
init_expr_once ()
{
rtx insn, pat;
enum machine_mode mode;
int num_clobbers;
rtx mem, mem1;
rtx reg;
/* Try indexing by frame ptr and try by stack ptr.
It is known that on the Convex the stack ptr isn't a valid index.
With luck, one or the other is valid on any machine. */
mem = gen_rtx_MEM (VOIDmode, stack_pointer_rtx);
mem1 = gen_rtx_MEM (VOIDmode, frame_pointer_rtx);
/* A scratch register we can modify in-place below to avoid
useless RTL allocations. */
reg = gen_rtx_REG (VOIDmode, -1);
insn = rtx_alloc (INSN);
pat = gen_rtx_SET (0, NULL_RTX, NULL_RTX);
PATTERN (insn) = pat;
for (mode = VOIDmode; (int) mode < NUM_MACHINE_MODES;
mode = (enum machine_mode) ((int) mode + 1))
{
int regno;
direct_load[(int) mode] = direct_store[(int) mode] = 0;
PUT_MODE (mem, mode);
PUT_MODE (mem1, mode);
PUT_MODE (reg, mode);
/* See if there is some register that can be used in this mode and
directly loaded or stored from memory. */
if (mode != VOIDmode && mode != BLKmode)
for (regno = 0; regno < FIRST_PSEUDO_REGISTER
&& (direct_load[(int) mode] == 0 || direct_store[(int) mode] == 0);
regno++)
{
if (! HARD_REGNO_MODE_OK (regno, mode))
continue;
REGNO (reg) = regno;
SET_SRC (pat) = mem;
SET_DEST (pat) = reg;
if (recog (pat, insn, &num_clobbers) >= 0)
direct_load[(int) mode] = 1;
SET_SRC (pat) = mem1;
SET_DEST (pat) = reg;
if (recog (pat, insn, &num_clobbers) >= 0)
direct_load[(int) mode] = 1;
SET_SRC (pat) = reg;
SET_DEST (pat) = mem;
if (recog (pat, insn, &num_clobbers) >= 0)
direct_store[(int) mode] = 1;
SET_SRC (pat) = reg;
SET_DEST (pat) = mem1;
if (recog (pat, insn, &num_clobbers) >= 0)
direct_store[(int) mode] = 1;
}
}
mem = gen_rtx_MEM (VOIDmode, gen_rtx_raw_REG (Pmode, 10000));
for (mode = GET_CLASS_NARROWEST_MODE (MODE_FLOAT); mode != VOIDmode;
mode = GET_MODE_WIDER_MODE (mode))
{
enum machine_mode srcmode;
for (srcmode = GET_CLASS_NARROWEST_MODE (MODE_FLOAT); srcmode != mode;
srcmode = GET_MODE_WIDER_MODE (srcmode))
{
enum insn_code ic;
ic = can_extend_p (mode, srcmode, 0);
if (ic == CODE_FOR_nothing)
continue;
PUT_MODE (mem, srcmode);
if ((*insn_data[ic].operand[1].predicate) (mem, srcmode))
float_extend_from_mem[mode][srcmode] = true;
}
}
}
/* This is run at the start of compiling a function. */
void
init_expr ()
{
cfun->expr = (struct expr_status *) ggc_alloc (sizeof (struct expr_status));
pending_chain = 0;
pending_stack_adjust = 0;
stack_pointer_delta = 0;
inhibit_defer_pop = 0;
saveregs_value = 0;
apply_args_value = 0;
forced_labels = 0;
}
/* Small sanity check that the queue is empty at the end of a function. */
void
finish_expr_for_function ()
{
if (pending_chain)
abort ();
}
/* Manage the queue of increment instructions to be output
for POSTINCREMENT_EXPR expressions, etc. */
/* Queue up to increment (or change) VAR later. BODY says how:
BODY should be the same thing you would pass to emit_insn
to increment right away. It will go to emit_insn later on.
The value is a QUEUED expression to be used in place of VAR
where you want to guarantee the pre-incrementation value of VAR. */
static rtx
enqueue_insn (var, body)
rtx var, body;
{
pending_chain = gen_rtx_QUEUED (GET_MODE (var), var, NULL_RTX, NULL_RTX,
body, pending_chain);
return pending_chain;
}
/* Use protect_from_queue to convert a QUEUED expression
into something that you can put immediately into an instruction.
If the queued incrementation has not happened yet,
protect_from_queue returns the variable itself.
If the incrementation has happened, protect_from_queue returns a temp
that contains a copy of the old value of the variable.
Any time an rtx which might possibly be a QUEUED is to be put
into an instruction, it must be passed through protect_from_queue first.
QUEUED expressions are not meaningful in instructions.
Do not pass a value through protect_from_queue and then hold
on to it for a while before putting it in an instruction!
If the queue is flushed in between, incorrect code will result. */
rtx
protect_from_queue (x, modify)
rtx x;
int modify;
{
RTX_CODE code = GET_CODE (x);
#if 0 /* A QUEUED can hang around after the queue is forced out. */
/* Shortcut for most common case. */
if (pending_chain == 0)
return x;
#endif
if (code != QUEUED)
{
/* A special hack for read access to (MEM (QUEUED ...)) to facilitate
use of autoincrement. Make a copy of the contents of the memory
location rather than a copy of the address, but not if the value is
of mode BLKmode. Don't modify X in place since it might be
shared. */
if (code == MEM && GET_MODE (x) != BLKmode
&& GET_CODE (XEXP (x, 0)) == QUEUED && !modify)
{
rtx y = XEXP (x, 0);
rtx new = replace_equiv_address_nv (x, QUEUED_VAR (y));
if (QUEUED_INSN (y))
{
rtx temp = gen_reg_rtx (GET_MODE (x));
emit_insn_before (gen_move_insn (temp, new),
QUEUED_INSN (y));
return temp;
}
/* Copy the address into a pseudo, so that the returned value
remains correct across calls to emit_queue. */
return replace_equiv_address (new, copy_to_reg (XEXP (new, 0)));
}
/* Otherwise, recursively protect the subexpressions of all
the kinds of rtx's that can contain a QUEUED. */
if (code == MEM)
{
rtx tem = protect_from_queue (XEXP (x, 0), 0);
if (tem != XEXP (x, 0))
{
x = copy_rtx (x);
XEXP (x, 0) = tem;
}
}
else if (code == PLUS || code == MULT)
{
rtx new0 = protect_from_queue (XEXP (x, 0), 0);
rtx new1 = protect_from_queue (XEXP (x, 1), 0);
if (new0 != XEXP (x, 0) || new1 != XEXP (x, 1))
{
x = copy_rtx (x);
XEXP (x, 0) = new0;
XEXP (x, 1) = new1;
}
}
return x;
}
/* If the increment has not happened, use the variable itself. Copy it
into a new pseudo so that the value remains correct across calls to
emit_queue. */
if (QUEUED_INSN (x) == 0)
return copy_to_reg (QUEUED_VAR (x));
/* If the increment has happened and a pre-increment copy exists,
use that copy. */
if (QUEUED_COPY (x) != 0)
return QUEUED_COPY (x);
/* The increment has happened but we haven't set up a pre-increment copy.
Set one up now, and use it. */
QUEUED_COPY (x) = gen_reg_rtx (GET_MODE (QUEUED_VAR (x)));
emit_insn_before (gen_move_insn (QUEUED_COPY (x), QUEUED_VAR (x)),
QUEUED_INSN (x));
return QUEUED_COPY (x);
}
/* Return nonzero if X contains a QUEUED expression:
if it contains anything that will be altered by a queued increment.
We handle only combinations of MEM, PLUS, MINUS and MULT operators
since memory addresses generally contain only those. */
int
queued_subexp_p (x)
rtx x;
{
enum rtx_code code = GET_CODE (x);
switch (code)
{
case QUEUED:
return 1;
case MEM:
return queued_subexp_p (XEXP (x, 0));
case MULT:
case PLUS:
case MINUS:
return (queued_subexp_p (XEXP (x, 0))
|| queued_subexp_p (XEXP (x, 1)));
default:
return 0;
}
}
/* Retrieve a mark on the queue. */
static rtx
mark_queue ()
{
return pending_chain;
}
/* Perform all the pending incrementations that have been enqueued
after MARK was retrieved. If MARK is null, perform all the
pending incrementations. */
static void
emit_insns_enqueued_after_mark (mark)
rtx mark;
{
rtx p;
/* The marked incrementation may have been emitted in the meantime
through a call to emit_queue. In this case, the mark is not valid
anymore so do nothing. */
if (mark && ! QUEUED_BODY (mark))
return;
while ((p = pending_chain) != mark)
{
rtx body = QUEUED_BODY (p);
switch (GET_CODE (body))
{
case INSN:
case JUMP_INSN:
case CALL_INSN:
case CODE_LABEL:
case BARRIER:
case NOTE:
QUEUED_INSN (p) = body;
emit_insn (body);
break;
#ifdef ENABLE_CHECKING
case SEQUENCE:
abort ();
break;
#endif
default:
QUEUED_INSN (p) = emit_insn (body);
break;
}
QUEUED_BODY (p) = 0;
pending_chain = QUEUED_NEXT (p);
}
}
/* Perform all the pending incrementations. */
void
emit_queue ()
{
emit_insns_enqueued_after_mark (NULL_RTX);
}
/* Copy data from FROM to TO, where the machine modes are not the same.
Both modes may be integer, or both may be floating.
UNSIGNEDP should be nonzero if FROM is an unsigned type.
This causes zero-extension instead of sign-extension. */
void
convert_move (to, from, unsignedp)
rtx to, from;
int unsignedp;
{
enum machine_mode to_mode = GET_MODE (to);
enum machine_mode from_mode = GET_MODE (from);
int to_real = GET_MODE_CLASS (to_mode) == MODE_FLOAT;
int from_real = GET_MODE_CLASS (from_mode) == MODE_FLOAT;
enum insn_code code;
rtx libcall;
/* rtx code for making an equivalent value. */
enum rtx_code equiv_code = (unsignedp < 0 ? UNKNOWN
: (unsignedp ? ZERO_EXTEND : SIGN_EXTEND));
to = protect_from_queue (to, 1);
from = protect_from_queue (from, 0);
if (to_real != from_real)
abort ();
/* If FROM is a SUBREG that indicates that we have already done at least
the required extension, strip it. We don't handle such SUBREGs as
TO here. */
if (GET_CODE (from) == SUBREG && SUBREG_PROMOTED_VAR_P (from)
&& (GET_MODE_SIZE (GET_MODE (SUBREG_REG (from)))
>= GET_MODE_SIZE (to_mode))
&& SUBREG_PROMOTED_UNSIGNED_P (from) == unsignedp)
from = gen_lowpart (to_mode, from), from_mode = to_mode;
if (GET_CODE (to) == SUBREG && SUBREG_PROMOTED_VAR_P (to))
abort ();
if (to_mode == from_mode
|| (from_mode == VOIDmode && CONSTANT_P (from)))
{
emit_move_insn (to, from);
return;
}
if (VECTOR_MODE_P (to_mode) || VECTOR_MODE_P (from_mode))
{
if (GET_MODE_BITSIZE (from_mode) != GET_MODE_BITSIZE (to_mode))
abort ();
if (VECTOR_MODE_P (to_mode))
from = simplify_gen_subreg (to_mode, from, GET_MODE (from), 0);
else
to = simplify_gen_subreg (from_mode, to, GET_MODE (to), 0);
emit_move_insn (to, from);
return;
}
if (to_real != from_real)
abort ();
if (to_real)
{
rtx value, insns;
if (GET_MODE_BITSIZE (from_mode) < GET_MODE_BITSIZE (to_mode))
{
/* Try converting directly if the insn is supported. */
if ((code = can_extend_p (to_mode, from_mode, 0))
!= CODE_FOR_nothing)
{
emit_unop_insn (code, to, from, UNKNOWN);
return;
}
}
#ifdef HAVE_trunchfqf2
if (HAVE_trunchfqf2 && from_mode == HFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_trunchfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctqfqf2
if (HAVE_trunctqfqf2 && from_mode == TQFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_trunctqfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncsfqf2
if (HAVE_truncsfqf2 && from_mode == SFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_truncsfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncdfqf2
if (HAVE_truncdfqf2 && from_mode == DFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_truncdfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncxfqf2
if (HAVE_truncxfqf2 && from_mode == XFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_truncxfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctfqf2
if (HAVE_trunctfqf2 && from_mode == TFmode && to_mode == QFmode)
{
emit_unop_insn (CODE_FOR_trunctfqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctqfhf2
if (HAVE_trunctqfhf2 && from_mode == TQFmode && to_mode == HFmode)
{
emit_unop_insn (CODE_FOR_trunctqfhf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncsfhf2
if (HAVE_truncsfhf2 && from_mode == SFmode && to_mode == HFmode)
{
emit_unop_insn (CODE_FOR_truncsfhf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncdfhf2
if (HAVE_truncdfhf2 && from_mode == DFmode && to_mode == HFmode)
{
emit_unop_insn (CODE_FOR_truncdfhf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncxfhf2
if (HAVE_truncxfhf2 && from_mode == XFmode && to_mode == HFmode)
{
emit_unop_insn (CODE_FOR_truncxfhf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctfhf2
if (HAVE_trunctfhf2 && from_mode == TFmode && to_mode == HFmode)
{
emit_unop_insn (CODE_FOR_trunctfhf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncsftqf2
if (HAVE_truncsftqf2 && from_mode == SFmode && to_mode == TQFmode)
{
emit_unop_insn (CODE_FOR_truncsftqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncdftqf2
if (HAVE_truncdftqf2 && from_mode == DFmode && to_mode == TQFmode)
{
emit_unop_insn (CODE_FOR_truncdftqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncxftqf2
if (HAVE_truncxftqf2 && from_mode == XFmode && to_mode == TQFmode)
{
emit_unop_insn (CODE_FOR_truncxftqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctftqf2
if (HAVE_trunctftqf2 && from_mode == TFmode && to_mode == TQFmode)
{
emit_unop_insn (CODE_FOR_trunctftqf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncdfsf2
if (HAVE_truncdfsf2 && from_mode == DFmode && to_mode == SFmode)
{
emit_unop_insn (CODE_FOR_truncdfsf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncxfsf2
if (HAVE_truncxfsf2 && from_mode == XFmode && to_mode == SFmode)
{
emit_unop_insn (CODE_FOR_truncxfsf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctfsf2
if (HAVE_trunctfsf2 && from_mode == TFmode && to_mode == SFmode)
{
emit_unop_insn (CODE_FOR_trunctfsf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_truncxfdf2
if (HAVE_truncxfdf2 && from_mode == XFmode && to_mode == DFmode)
{
emit_unop_insn (CODE_FOR_truncxfdf2, to, from, UNKNOWN);
return;
}
#endif
#ifdef HAVE_trunctfdf2
if (HAVE_trunctfdf2 && from_mode == TFmode && to_mode == DFmode)
{
emit_unop_insn (CODE_FOR_trunctfdf2, to, from, UNKNOWN);
return;
}
#endif
libcall = (rtx) 0;
switch (from_mode)
{
case SFmode:
switch (to_mode)
{
case DFmode:
libcall = extendsfdf2_libfunc;
break;
case XFmode:
libcall = extendsfxf2_libfunc;
break;
case TFmode:
libcall = extendsftf2_libfunc;
break;
default:
break;
}
break;
case DFmode:
switch (to_mode)
{
case SFmode:
libcall = truncdfsf2_libfunc;
break;
case XFmode:
libcall = extenddfxf2_libfunc;
break;
case TFmode:
libcall = extenddftf2_libfunc;
break;
default:
break;
}
break;
case XFmode:
switch (to_mode)
{
case SFmode:
libcall = truncxfsf2_libfunc;
break;
case DFmode:
libcall = truncxfdf2_libfunc;
break;
default:
break;
}
break;
case TFmode:
switch (to_mode)
{
case SFmode:
libcall = trunctfsf2_libfunc;
break;
case DFmode:
libcall = trunctfdf2_libfunc;
break;
default:
break;
}
break;
default:
break;
}
if (libcall == (rtx) 0)
/* This conversion is not implemented yet. */
abort ();
start_sequence ();
value = emit_library_call_value (libcall, NULL_RTX, LCT_CONST, to_mode,
1, from, from_mode);
insns = get_insns ();
end_sequence ();
emit_libcall_block (insns, to, value, gen_rtx_FLOAT_TRUNCATE (to_mode,
from));
return;
}
/* Now both modes are integers. */
/* Handle expanding beyond a word. */
if (GET_MODE_BITSIZE (from_mode) < GET_MODE_BITSIZE (to_mode)
&& GET_MODE_BITSIZE (to_mode) > BITS_PER_WORD)
{
rtx insns;
rtx lowpart;
rtx fill_value;
rtx lowfrom;
int i;
enum machine_mode lowpart_mode;
int nwords = CEIL (GET_MODE_SIZE (to_mode), UNITS_PER_WORD);
/* Try converting directly if the insn is supported. */
if ((code = can_extend_p (to_mode, from_mode, unsignedp))
!= CODE_FOR_nothing)
{
/* If FROM is a SUBREG, put it into a register. Do this
so that we always generate the same set of insns for
better cse'ing; if an intermediate assignment occurred,
we won't be doing the operation directly on the SUBREG. */
if (optimize > 0 && GET_CODE (from) == SUBREG)
from = force_reg (from_mode, from);
emit_unop_insn (code, to, from, equiv_code);
return;
}
/* Next, try converting via full word. */
else if (GET_MODE_BITSIZE (from_mode) < BITS_PER_WORD
&& ((code = can_extend_p (to_mode, word_mode, unsignedp))
!= CODE_FOR_nothing))
{
if (GET_CODE (to) == REG)
{
if (reg_overlap_mentioned_p (to, from))
from = force_reg (from_mode, from);
emit_insn (gen_rtx_CLOBBER (VOIDmode, to));
}
convert_move (gen_lowpart (word_mode, to), from, unsignedp);
emit_unop_insn (code, to,
gen_lowpart (word_mode, to), equiv_code);
return;
}
/* No special multiword conversion insn; do it by hand. */
start_sequence ();
/* Since we will turn this into a no conflict block, we must ensure
that the source does not overlap the target. */
if (reg_overlap_mentioned_p (to, from))
from = force_reg (from_mode, from);
/* Get a copy of FROM widened to a word, if necessary. */
if (GET_MODE_BITSIZE (from_mode) < BITS_PER_WORD)
lowpart_mode = word_mode;
else
lowpart_mode = from_mode;
lowfrom = convert_to_mode (lowpart_mode, from, unsignedp);
lowpart = gen_lowpart (lowpart_mode, to);
emit_move_insn (lowpart, lowfrom);
/* Compute the value to put in each remaining word. */
if (unsignedp)
fill_value = const0_rtx;
else
{
#ifdef HAVE_slt
if (HAVE_slt
&& insn_data[(int) CODE_FOR_slt].operand[0].mode == word_mode
&& STORE_FLAG_VALUE == -1)
{
emit_cmp_insn (lowfrom, const0_rtx, NE, NULL_RTX,
lowpart_mode, 0);
fill_value = gen_reg_rtx (word_mode);
emit_insn (gen_slt (fill_value));
}
else
#endif
{
fill_value
= expand_shift (RSHIFT_EXPR, lowpart_mode, lowfrom,
size_int (GET_MODE_BITSIZE (lowpart_mode) - 1),
NULL_RTX, 0);
fill_value = convert_to_mode (word_mode, fill_value, 1);
}
}
/* Fill the remaining words. */
for (i = GET_MODE_SIZE (lowpart_mode) / UNITS_PER_WORD; i < nwords; i++)
{
int index = (WORDS_BIG_ENDIAN ? nwords - i - 1 : i);
rtx subword = operand_subword (to, index, 1, to_mode);
if (subword == 0)
abort ();
if (fill_value != subword)
emit_move_insn (subword, fill_value);
}
insns = get_insns ();
end_sequence ();
emit_no_conflict_block (insns, to, from, NULL_RTX,
gen_rtx_fmt_e (equiv_code, to_mode, copy_rtx (from)));
return;
}
/* Truncating multi-word to a word or less. */
if (GET_MODE_BITSIZE (from_mode) > BITS_PER_WORD
&& GET_MODE_BITSIZE (to_mode) <= BITS_PER_WORD)
{
if (!((GET_CODE (from) == MEM
&& ! MEM_VOLATILE_P (from)
&& direct_load[(int) to_mode]
&& ! mode_dependent_address_p (XEXP (from, 0)))
|| GET_CODE (from) == REG
|| GET_CODE (from) == SUBREG))
from = force_reg (from_mode, from);
convert_move (to, gen_lowpart (word_mode, from), 0);
return;
}
/* Handle pointer conversion. */ /* SPEE 900220. */
if (to_mode == PQImode)
{
if (from_mode != QImode)
from = convert_to_mode (QImode, from, unsignedp);
#ifdef HAVE_truncqipqi2
if (HAVE_truncqipqi2)
{
emit_unop_insn (CODE_FOR_truncqipqi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_truncqipqi2 */
abort ();
}
if (from_mode == PQImode)
{
if (to_mode != QImode)
{
from = convert_to_mode (QImode, from, unsignedp);
from_mode = QImode;
}
else
{
#ifdef HAVE_extendpqiqi2
if (HAVE_extendpqiqi2)
{
emit_unop_insn (CODE_FOR_extendpqiqi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_extendpqiqi2 */
abort ();
}
}
if (to_mode == PSImode)
{
if (from_mode != SImode)
from = convert_to_mode (SImode, from, unsignedp);
#ifdef HAVE_truncsipsi2
if (HAVE_truncsipsi2)
{
emit_unop_insn (CODE_FOR_truncsipsi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_truncsipsi2 */
abort ();
}
if (from_mode == PSImode)
{
if (to_mode != SImode)
{
from = convert_to_mode (SImode, from, unsignedp);
from_mode = SImode;
}
else
{
#ifdef HAVE_extendpsisi2
if (! unsignedp && HAVE_extendpsisi2)
{
emit_unop_insn (CODE_FOR_extendpsisi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_extendpsisi2 */
#ifdef HAVE_zero_extendpsisi2
if (unsignedp && HAVE_zero_extendpsisi2)
{
emit_unop_insn (CODE_FOR_zero_extendpsisi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_zero_extendpsisi2 */
abort ();
}
}
if (to_mode == PDImode)
{
if (from_mode != DImode)
from = convert_to_mode (DImode, from, unsignedp);
#ifdef HAVE_truncdipdi2
if (HAVE_truncdipdi2)
{
emit_unop_insn (CODE_FOR_truncdipdi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_truncdipdi2 */
abort ();
}
if (from_mode == PDImode)
{
if (to_mode != DImode)
{
from = convert_to_mode (DImode, from, unsignedp);
from_mode = DImode;
}
else
{
#ifdef HAVE_extendpdidi2
if (HAVE_extendpdidi2)
{
emit_unop_insn (CODE_FOR_extendpdidi2, to, from, UNKNOWN);
return;
}
#endif /* HAVE_extendpdidi2 */
abort ();
}
}
/* Now follow all the conversions between integers
no more than a word long. */
/* For truncation, usually we can just refer to FROM in a narrower mode. */
if (GET_MODE_BITSIZE (to_mode) < GET_MODE_BITSIZE (from_mode)
&& TRULY_NOOP_TRUNCATION (GET_MODE_BITSIZE (to_mode),
GET_MODE_BITSIZE (from_mode)))
{
if (!((GET_CODE (from) == MEM
&& ! MEM_VOLATILE_P (from)
&& direct_load[(int) to_mode]
&& ! mode_dependent_address_p (XEXP (from, 0)))
|| GET_CODE (from) == REG
|| GET_CODE (from) == SUBREG))
from = force_reg (from_mode, from);
if (GET_CODE (from) == REG && REGNO (from) < FIRST_PSEUDO_REGISTER
&& ! HARD_REGNO_MODE_OK (REGNO (from), to_mode))
from = copy_to_reg (from);
emit_move_insn (to, gen_lowpart (to_mode, from));
return;
}
/* Handle extension. */
if (GET_MODE_BITSIZE (to_mode) > GET_MODE_BITSIZE (from_mode))
{
/* Convert directly if that works. */
if ((code = can_extend_p (to_mode, from_mode, unsignedp))
!= CODE_FOR_nothing)
{
if (flag_force_mem)
from = force_not_mem (from);
emit_unop_insn (code, to, from, equiv_code);
return;
}
else
{
enum machine_mode intermediate;
rtx tmp;
tree shift_amount;
/* Search for a mode to convert via. */
for (intermediate = from_mode; intermediate != VOIDmode;
intermediate = GET_MODE_WIDER_MODE (intermediate))
if (((can_extend_p (to_mode, intermediate, unsignedp)
!= CODE_FOR_nothing)
|| (GET_MODE_SIZE (to_mode) < GET_MODE_SIZE (intermediate)
&& TRULY_NOOP_TRUNCATION (GET_MODE_BITSIZE (to_mode),
GET_MODE_BITSIZE (intermediate))))
&& (can_extend_p (intermediate, from_mode, unsignedp)
!= CODE_FOR_nothing))
{
convert_move (to, convert_to_mode (intermediate, from,
unsignedp), unsignedp);
return;
}
/* No suitable intermediate mode.
Generate what we need with shifts. */
shift_amount = build_int_2 (GET_MODE_BITSIZE (to_mode)
- GET_MODE_BITSIZE (from_mode), 0);
from = gen_lowpart (to_mode, force_reg (from_mode, from));
tmp = expand_shift (LSHIFT_EXPR, to_mode, from, shift_amount,
to, unsignedp);
tmp = expand_shift (RSHIFT_EXPR, to_mode, tmp, shift_amount,
to, unsignedp);
if (tmp != to)
emit_move_insn (to, tmp);
return;
}
}
/* Support special truncate insns for certain modes. */
if (from_mode == DImode && to_mode == SImode)
{
#ifdef HAVE_truncdisi2
if (HAVE_truncdisi2)
{
emit_unop_insn (CODE_FOR_truncdisi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == DImode && to_mode == HImode)
{
#ifdef HAVE_truncdihi2
if (HAVE_truncdihi2)
{
emit_unop_insn (CODE_FOR_truncdihi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == DImode && to_mode == QImode)
{
#ifdef HAVE_truncdiqi2
if (HAVE_truncdiqi2)
{
emit_unop_insn (CODE_FOR_truncdiqi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == SImode && to_mode == HImode)
{
#ifdef HAVE_truncsihi2
if (HAVE_truncsihi2)
{
emit_unop_insn (CODE_FOR_truncsihi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == SImode && to_mode == QImode)
{
#ifdef HAVE_truncsiqi2
if (HAVE_truncsiqi2)
{
emit_unop_insn (CODE_FOR_truncsiqi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == HImode && to_mode == QImode)
{
#ifdef HAVE_trunchiqi2
if (HAVE_trunchiqi2)
{
emit_unop_insn (CODE_FOR_trunchiqi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == TImode && to_mode == DImode)
{
#ifdef HAVE_trunctidi2
if (HAVE_trunctidi2)
{
emit_unop_insn (CODE_FOR_trunctidi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == TImode && to_mode == SImode)
{
#ifdef HAVE_trunctisi2
if (HAVE_trunctisi2)
{
emit_unop_insn (CODE_FOR_trunctisi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == TImode && to_mode == HImode)
{
#ifdef HAVE_trunctihi2
if (HAVE_trunctihi2)
{
emit_unop_insn (CODE_FOR_trunctihi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
if (from_mode == TImode && to_mode == QImode)
{
#ifdef HAVE_trunctiqi2
if (HAVE_trunctiqi2)
{
emit_unop_insn (CODE_FOR_trunctiqi2, to, from, UNKNOWN);
return;
}
#endif
convert_move (to, force_reg (from_mode, from), unsignedp);
return;
}
/* Handle truncation of volatile memrefs, and so on;
the things that couldn't be truncated directly,
and for which there was no special instruction. */
if (GET_MODE_BITSIZE (to_mode) < GET_MODE_BITSIZE (from_mode))
{
rtx temp = force_reg (to_mode, gen_lowpart (to_mode, from));
emit_move_insn (to, temp);
return;
}
/* Mode combination is not recognized. */
abort ();
}
/* Return an rtx for a value that would result
from converting X to mode MODE.
Both X and MODE may be floating, or both integer.
UNSIGNEDP is nonzero if X is an unsigned value.
This can be done by referring to a part of X in place
or by copying to a new temporary with conversion.
This function *must not* call protect_from_queue
except when putting X into an insn (in which case convert_move does it). */
rtx
convert_to_mode (mode, x, unsignedp)
enum machine_mode mode;
rtx x;
int unsignedp;
{
return convert_modes (mode, VOIDmode, x, unsignedp);
}
/* Return an rtx for a value that would result
from converting X from mode OLDMODE to mode MODE.
Both modes may be floating, or both integer.
UNSIGNEDP is nonzero if X is an unsigned value.
This can be done by referring to a part of X in place
or by copying to a new temporary with conversion.
You can give VOIDmode for OLDMODE, if you are sure X has a nonvoid mode.
This function *must not* call protect_from_queue
except when putting X into an insn (in which case convert_move does it). */
rtx
convert_modes (mode, oldmode, x, unsignedp)
enum machine_mode mode, oldmode;
rtx x;
int unsignedp;
{
rtx temp;
/* If FROM is a SUBREG that indicates that we have already done at least
the required extension, strip it. */
if (GET_CODE (x) == SUBREG && SUBREG_PROMOTED_VAR_P (x)
&& GET_MODE_SIZE (GET_MODE (SUBREG_REG (x))) >= GET_MODE_SIZE (mode)
&& SUBREG_PROMOTED_UNSIGNED_P (x) == unsignedp)
x = gen_lowpart (mode, x);
if (GET_MODE (x) != VOIDmode)
oldmode = GET_MODE (x);
if (mode == oldmode)
return x;
/* There is one case that we must handle specially: If we are converting
a CONST_INT into a mode whose size is twice HOST_BITS_PER_WIDE_INT and
we are to interpret the constant as unsigned, gen_lowpart will do
the wrong if the constant appears negative. What we want to do is
make the high-order word of the constant zero, not all ones. */
if (unsignedp && GET_MODE_CLASS (mode) == MODE_INT
&& GET_MODE_BITSIZE (mode) == 2 * HOST_BITS_PER_WIDE_INT
&& GET_CODE (x) == CONST_INT && INTVAL (x) < 0)
{
HOST_WIDE_INT val = INTVAL (x);
if (oldmode != VOIDmode
&& HOST_BITS_PER_WIDE_INT > GET_MODE_BITSIZE (oldmode))
{
int width = GET_MODE_BITSIZE (oldmode);
/* We need to zero extend VAL. */
val &= ((HOST_WIDE_INT) 1 << width) - 1;
}
return immed_double_const (val, (HOST_WIDE_INT) 0, mode);
}
/* We can do this with a gen_lowpart if both desired and current modes
are integer, and this is either a constant integer, a register, or a
non-volatile MEM. Except for the constant case where MODE is no
wider than HOST_BITS_PER_WIDE_INT, we must be narrowing the operand. */
if ((GET_CODE (x) == CONST_INT
&& GET_MODE_BITSIZE (mode) <= HOST_BITS_PER_WIDE_INT)
|| (GET_MODE_CLASS (mode) == MODE_INT
&& GET_MODE_CLASS (oldmode) == MODE_INT
&& (GET_CODE (x) == CONST_DOUBLE
|| (GET_MODE_SIZE (mode) <= GET_MODE_SIZE (oldmode)
&& ((GET_CODE (x) == MEM && ! MEM_VOLATILE_P (x)
&& direct_load[(int) mode])
|| (GET_CODE (x) == REG
&& (! HARD_REGISTER_P (x)
|| HARD_REGNO_MODE_OK (REGNO (x), mode))
&& TRULY_NOOP_TRUNCATION (GET_MODE_BITSIZE (mode),
GET_MODE_BITSIZE (GET_MODE (x)))))))))
{
/* ?? If we don't know OLDMODE, we have to assume here that
X does not need sign- or zero-extension. This may not be
the case, but it's the best we can do. */
if (GET_CODE (x) == CONST_INT && oldmode != VOIDmode
&& GET_MODE_SIZE (mode) > GET_MODE_SIZE (oldmode))
{
HOST_WIDE_INT val = INTVAL (x);
int width = GET_MODE_BITSIZE (oldmode);
/* We must sign or zero-extend in this case. Start by
zero-extending, then sign extend if we need to. */
val &= ((HOST_WIDE_INT) 1 << width) - 1;
if (! unsignedp
&& (val & ((HOST_WIDE_INT) 1 << (width - 1))))
val |= (HOST_WIDE_INT) (-1) << width;
return gen_int_mode (val, mode);
}
return gen_lowpart (mode, x);
}
/* Converting from integer constant into mode is always equivalent to an
subreg operation. */
if (VECTOR_MODE_P (mode) && GET_MODE (x) == VOIDmode)
{
if (GET_MODE_BITSIZE (mode) != GET_MODE_BITSIZE (oldmode))
abort ();
return simplify_gen_subreg (mode, x, oldmode, 0);
}
temp = gen_reg_rtx (mode);
convert_move (temp, x, unsignedp);
return temp;
}
/* This macro is used to determine what the largest unit size that
move_by_pieces can use is. */
/* MOVE_MAX_PIECES is the number of bytes at a time which we can
move efficiently, as opposed to MOVE_MAX which is the maximum
number of bytes we can move with a single instruction. */
#ifndef MOVE_MAX_PIECES
#define MOVE_MAX_PIECES MOVE_MAX
#endif
/* STORE_MAX_PIECES is the number of bytes at a time that we can
store efficiently. Due to internal GCC limitations, this is
MOVE_MAX_PIECES limited by the number of bytes GCC can represent
for an immediate constant. */
#define STORE_MAX_PIECES MIN (MOVE_MAX_PIECES, 2 * sizeof (HOST_WIDE_INT))
/* Generate several move instructions to copy LEN bytes from block FROM to
block TO. (These are MEM rtx's with BLKmode). The caller must pass FROM
and TO through protect_from_queue before calling.
If PUSH_ROUNDING is defined and TO is NULL, emit_single_push_insn is
used to push FROM to the stack.
ALIGN is maximum alignment we can assume. */
void
move_by_pieces (to, from, len, align)
rtx to, from;
unsigned HOST_WIDE_INT len;
unsigned int align;
{
struct move_by_pieces data;
rtx to_addr, from_addr = XEXP (from, 0);
unsigned int max_size = MOVE_MAX_PIECES + 1;
enum machine_mode mode = VOIDmode, tmode;
enum insn_code icode;
data.offset = 0;
data.from_addr = from_addr;
if (to)
{
to_addr = XEXP (to, 0);
data.to = to;
data.autinc_to
= (GET_CODE (to_addr) == PRE_INC || GET_CODE (to_addr) == PRE_DEC
|| GET_CODE (to_addr) == POST_INC || GET_CODE (to_addr) == POST_DEC);
data.reverse
= (GET_CODE (to_addr) == PRE_DEC || GET_CODE (to_addr) == POST_DEC);
}
else
{
to_addr = NULL_RTX;
data.to = NULL_RTX;
data.autinc_to = 1;
#ifdef STACK_GROWS_DOWNWARD
data.reverse = 1;
#else
data.reverse = 0;
#endif
}
data.to_addr = to_addr;
data.from = from;
data.autinc_from
= (GET_CODE (from_addr) == PRE_INC || GET_CODE (from_addr) == PRE_DEC
|| GET_CODE (from_addr) == POST_INC
|| GET_CODE (from_addr) == POST_DEC);
data.explicit_inc_from = 0;
data.explicit_inc_to = 0;
if (data.reverse) data.offset = len;
data.len = len;
/* If copying requires more than two move insns,
copy addresses to registers (to make displacements shorter)
and use post-increment if available. */
if (!(data.autinc_from && data.autinc_to)
&& move_by_pieces_ninsns (len, align) > 2)
{
/* Find the mode of the largest move... */
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (USE_LOAD_PRE_DECREMENT (mode) && data.reverse && ! data.autinc_from)
{
data.from_addr = copy_addr_to_reg (plus_constant (from_addr, len));
data.autinc_from = 1;
data.explicit_inc_from = -1;
}
if (USE_LOAD_POST_INCREMENT (mode) && ! data.autinc_from)
{
data.from_addr = copy_addr_to_reg (from_addr);
data.autinc_from = 1;
data.explicit_inc_from = 1;
}
if (!data.autinc_from && CONSTANT_P (from_addr))
data.from_addr = copy_addr_to_reg (from_addr);
if (USE_STORE_PRE_DECREMENT (mode) && data.reverse && ! data.autinc_to)
{
data.to_addr = copy_addr_to_reg (plus_constant (to_addr, len));
data.autinc_to = 1;
data.explicit_inc_to = -1;
}
if (USE_STORE_POST_INCREMENT (mode) && ! data.reverse && ! data.autinc_to)
{
data.to_addr = copy_addr_to_reg (to_addr);
data.autinc_to = 1;
data.explicit_inc_to = 1;
}
if (!data.autinc_to && CONSTANT_P (to_addr))
data.to_addr = copy_addr_to_reg (to_addr);
}
if (! SLOW_UNALIGNED_ACCESS (word_mode, align)
|| align > MOVE_MAX * BITS_PER_UNIT || align >= BIGGEST_ALIGNMENT)
align = MOVE_MAX * BITS_PER_UNIT;
/* First move what we can in the largest integer mode, then go to
successively smaller modes. */
while (max_size > 1)
{
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (mode == VOIDmode)
break;
icode = mov_optab->handlers[(int) mode].insn_code;
if (icode != CODE_FOR_nothing && align >= GET_MODE_ALIGNMENT (mode))
move_by_pieces_1 (GEN_FCN (icode), mode, &data);
max_size = GET_MODE_SIZE (mode);
}
/* The code above should have handled everything. */
if (data.len > 0)
abort ();
}
/* Return number of insns required to move L bytes by pieces.
ALIGN (in bits) is maximum alignment we can assume. */
static unsigned HOST_WIDE_INT
move_by_pieces_ninsns (l, align)
unsigned HOST_WIDE_INT l;
unsigned int align;
{
unsigned HOST_WIDE_INT n_insns = 0;
unsigned HOST_WIDE_INT max_size = MOVE_MAX + 1;
if (! SLOW_UNALIGNED_ACCESS (word_mode, align)
|| align > MOVE_MAX * BITS_PER_UNIT || align >= BIGGEST_ALIGNMENT)
align = MOVE_MAX * BITS_PER_UNIT;
while (max_size > 1)
{
enum machine_mode mode = VOIDmode, tmode;
enum insn_code icode;
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (mode == VOIDmode)
break;
icode = mov_optab->handlers[(int) mode].insn_code;
if (icode != CODE_FOR_nothing && align >= GET_MODE_ALIGNMENT (mode))
n_insns += l / GET_MODE_SIZE (mode), l %= GET_MODE_SIZE (mode);
max_size = GET_MODE_SIZE (mode);
}
if (l)
abort ();
return n_insns;
}
/* Subroutine of move_by_pieces. Move as many bytes as appropriate
with move instructions for mode MODE. GENFUN is the gen_... function
to make a move insn for that mode. DATA has all the other info. */
static void
move_by_pieces_1 (genfun, mode, data)
rtx (*genfun) PARAMS ((rtx, ...));
enum machine_mode mode;
struct move_by_pieces *data;
{
unsigned int size = GET_MODE_SIZE (mode);
rtx to1 = NULL_RTX, from1;
while (data->len >= size)
{
if (data->reverse)
data->offset -= size;
if (data->to)
{
if (data->autinc_to)
to1 = adjust_automodify_address (data->to, mode, data->to_addr,
data->offset);
else
to1 = adjust_address (data->to, mode, data->offset);
}
if (data->autinc_from)
from1 = adjust_automodify_address (data->from, mode, data->from_addr,
data->offset);
else
from1 = adjust_address (data->from, mode, data->offset);
if (HAVE_PRE_DECREMENT && data->explicit_inc_to < 0)
emit_insn (gen_add2_insn (data->to_addr,
GEN_INT (-(HOST_WIDE_INT)size)));
if (HAVE_PRE_DECREMENT && data->explicit_inc_from < 0)
emit_insn (gen_add2_insn (data->from_addr,
GEN_INT (-(HOST_WIDE_INT)size)));
if (data->to)
emit_insn ((*genfun) (to1, from1));
else
{
#ifdef PUSH_ROUNDING
emit_single_push_insn (mode, from1, NULL);
#else
abort ();
#endif
}
if (HAVE_POST_INCREMENT && data->explicit_inc_to > 0)
emit_insn (gen_add2_insn (data->to_addr, GEN_INT (size)));
if (HAVE_POST_INCREMENT && data->explicit_inc_from > 0)
emit_insn (gen_add2_insn (data->from_addr, GEN_INT (size)));
if (! data->reverse)
data->offset += size;
data->len -= size;
}
}
/* Emit code to move a block Y to a block X. This may be done with
string-move instructions, with multiple scalar move instructions,
or with a library call.
Both X and Y must be MEM rtx's (perhaps inside VOLATILE) with mode BLKmode.
SIZE is an rtx that says how long they are.
ALIGN is the maximum alignment we can assume they have.
METHOD describes what kind of copy this is, and what mechanisms may be used.
Return the address of the new block, if memcpy is called and returns it,
0 otherwise. */
rtx
emit_block_move (x, y, size, method)
rtx x, y, size;
enum block_op_methods method;
{
bool may_use_call;
rtx retval = 0;
unsigned int align;
switch (method)
{
case BLOCK_OP_NORMAL:
may_use_call = true;
break;
case BLOCK_OP_CALL_PARM:
may_use_call = block_move_libcall_safe_for_call_parm ();
/* Make inhibit_defer_pop nonzero around the library call
to force it to pop the arguments right away. */
NO_DEFER_POP;
break;
case BLOCK_OP_NO_LIBCALL:
may_use_call = false;
break;
default:
abort ();
}
align = MIN (MEM_ALIGN (x), MEM_ALIGN (y));
if (GET_MODE (x) != BLKmode)
abort ();
if (GET_MODE (y) != BLKmode)
abort ();
x = protect_from_queue (x, 1);
y = protect_from_queue (y, 0);
size = protect_from_queue (size, 0);
if (GET_CODE (x) != MEM)
abort ();
if (GET_CODE (y) != MEM)
abort ();
if (size == 0)
abort ();
/* Set MEM_SIZE as appropriate for this block copy. The main place this
can be incorrect is coming from __builtin_memcpy. */
if (GET_CODE (size) == CONST_INT)
{
x = shallow_copy_rtx (x);
y = shallow_copy_rtx (y);
set_mem_size (x, size);
set_mem_size (y, size);
}
if (GET_CODE (size) == CONST_INT && MOVE_BY_PIECES_P (INTVAL (size), align))
move_by_pieces (x, y, INTVAL (size), align);
else if (emit_block_move_via_movstr (x, y, size, align))
;
else if (may_use_call)
retval = emit_block_move_via_libcall (x, y, size);
else
emit_block_move_via_loop (x, y, size, align);
if (method == BLOCK_OP_CALL_PARM)
OK_DEFER_POP;
return retval;
}
/* A subroutine of emit_block_move. Returns true if calling the
block move libcall will not clobber any parameters which may have
already been placed on the stack. */
static bool
block_move_libcall_safe_for_call_parm ()
{
if (PUSH_ARGS)
return true;
else
{
/* Check to see whether memcpy takes all register arguments. */
static enum {
takes_regs_uninit, takes_regs_no, takes_regs_yes
} takes_regs = takes_regs_uninit;
switch (takes_regs)
{
case takes_regs_uninit:
{
CUMULATIVE_ARGS args_so_far;
tree fn, arg;
fn = emit_block_move_libcall_fn (false);
INIT_CUMULATIVE_ARGS (args_so_far, TREE_TYPE (fn), NULL_RTX, 0);
arg = TYPE_ARG_TYPES (TREE_TYPE (fn));
for ( ; arg != void_list_node ; arg = TREE_CHAIN (arg))
{
enum machine_mode mode = TYPE_MODE (TREE_VALUE (arg));
rtx tmp = FUNCTION_ARG (args_so_far, mode, NULL_TREE, 1);
if (!tmp || !REG_P (tmp))
goto fail_takes_regs;
#ifdef FUNCTION_ARG_PARTIAL_NREGS
if (FUNCTION_ARG_PARTIAL_NREGS (args_so_far, mode,
NULL_TREE, 1))
goto fail_takes_regs;
#endif
FUNCTION_ARG_ADVANCE (args_so_far, mode, NULL_TREE, 1);
}
}
takes_regs = takes_regs_yes;
/* FALLTHRU */
case takes_regs_yes:
return true;
fail_takes_regs:
takes_regs = takes_regs_no;
/* FALLTHRU */
case takes_regs_no:
return false;
default:
abort ();
}
}
}
/* A subroutine of emit_block_move. Expand a movstr pattern;
return true if successful. */
static bool
emit_block_move_via_movstr (x, y, size, align)
rtx x, y, size;
unsigned int align;
{
/* Try the most limited insn first, because there's no point
including more than one in the machine description unless
the more limited one has some advantage. */
rtx opalign = GEN_INT (align / BITS_PER_UNIT);
enum machine_mode mode;
/* Since this is a move insn, we don't care about volatility. */
volatile_ok = 1;
for (mode = GET_CLASS_NARROWEST_MODE (MODE_INT); mode != VOIDmode;
mode = GET_MODE_WIDER_MODE (mode))
{
enum insn_code code = movstr_optab[(int) mode];
insn_operand_predicate_fn pred;
if (code != CODE_FOR_nothing
/* We don't need MODE to be narrower than BITS_PER_HOST_WIDE_INT
here because if SIZE is less than the mode mask, as it is
returned by the macro, it will definitely be less than the
actual mode mask. */
&& ((GET_CODE (size) == CONST_INT
&& ((unsigned HOST_WIDE_INT) INTVAL (size)
<= (GET_MODE_MASK (mode) >> 1)))
|| GET_MODE_BITSIZE (mode) >= BITS_PER_WORD)
&& ((pred = insn_data[(int) code].operand[0].predicate) == 0
|| (*pred) (x, BLKmode))
&& ((pred = insn_data[(int) code].operand[1].predicate) == 0
|| (*pred) (y, BLKmode))
&& ((pred = insn_data[(int) code].operand[3].predicate) == 0
|| (*pred) (opalign, VOIDmode)))
{
rtx op2;
rtx last = get_last_insn ();
rtx pat;
op2 = convert_to_mode (mode, size, 1);
pred = insn_data[(int) code].operand[2].predicate;
if (pred != 0 && ! (*pred) (op2, mode))
op2 = copy_to_mode_reg (mode, op2);
/* ??? When called via emit_block_move_for_call, it'd be
nice if there were some way to inform the backend, so
that it doesn't fail the expansion because it thinks
emitting the libcall would be more efficient. */
pat = GEN_FCN ((int) code) (x, y, op2, opalign);
if (pat)
{
emit_insn (pat);
volatile_ok = 0;
return true;
}
else
delete_insns_since (last);
}
}
volatile_ok = 0;
return false;
}
/* A subroutine of emit_block_move. Expand a call to memcpy or bcopy.
Return the return value from memcpy, 0 otherwise. */
static rtx
emit_block_move_via_libcall (dst, src, size)
rtx dst, src, size;
{
tree call_expr, arg_list, fn, src_tree, dst_tree, size_tree;
enum machine_mode size_mode;
rtx retval;
/* DST, SRC, or SIZE may have been passed through protect_from_queue.
It is unsafe to save the value generated by protect_from_queue
and reuse it later. Consider what happens if emit_queue is
called before the return value from protect_from_queue is used.
Expansion of the CALL_EXPR below will call emit_queue before
we are finished emitting RTL for argument setup. So if we are
not careful we could get the wrong value for an argument.
To avoid this problem we go ahead and emit code to copy X, Y &
SIZE into new pseudos. We can then place those new pseudos
into an RTL_EXPR and use them later, even after a call to
emit_queue.
Note this is not strictly needed for library calls since they
do not call emit_queue before loading their arguments. However,
we may need to have library calls call emit_queue in the future
since failing to do so could cause problems for targets which
define SMALL_REGISTER_CLASSES and pass arguments in registers. */
dst = copy_to_mode_reg (Pmode, XEXP (dst, 0));
src = copy_to_mode_reg (Pmode, XEXP (src, 0));
if (TARGET_MEM_FUNCTIONS)
size_mode = TYPE_MODE (sizetype);
else
size_mode = TYPE_MODE (unsigned_type_node);
size = convert_to_mode (size_mode, size, 1);
size = copy_to_mode_reg (size_mode, size);
/* It is incorrect to use the libcall calling conventions to call
memcpy in this context. This could be a user call to memcpy and
the user may wish to examine the return value from memcpy. For
targets where libcalls and normal calls have different conventions
for returning pointers, we could end up generating incorrect code.
For convenience, we generate the call to bcopy this way as well. */
dst_tree = make_tree (ptr_type_node, dst);
src_tree = make_tree (ptr_type_node, src);
if (TARGET_MEM_FUNCTIONS)
size_tree = make_tree (sizetype, size);
else
size_tree = make_tree (unsigned_type_node, size);
fn = emit_block_move_libcall_fn (true);
arg_list = tree_cons (NULL_TREE, size_tree, NULL_TREE);
if (TARGET_MEM_FUNCTIONS)
{
arg_list = tree_cons (NULL_TREE, src_tree, arg_list);
arg_list = tree_cons (NULL_TREE, dst_tree, arg_list);
}
else
{
arg_list = tree_cons (NULL_TREE, dst_tree, arg_list);
arg_list = tree_cons (NULL_TREE, src_tree, arg_list);
}
/* Now we have to build up the CALL_EXPR itself. */
call_expr = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (fn)), fn);
call_expr = build (CALL_EXPR, TREE_TYPE (TREE_TYPE (fn)),
call_expr, arg_list, NULL_TREE);
TREE_SIDE_EFFECTS (call_expr) = 1;
retval = expand_expr (call_expr, NULL_RTX, VOIDmode, 0);
/* If we are initializing a readonly value, show the above call
clobbered it. Otherwise, a load from it may erroneously be
hoisted from a loop. */
if (RTX_UNCHANGING_P (dst))
emit_insn (gen_rtx_CLOBBER (VOIDmode, dst));
return (TARGET_MEM_FUNCTIONS ? retval : NULL_RTX);
}
/* A subroutine of emit_block_move_via_libcall. Create the tree node
for the function we use for block copies. The first time FOR_CALL
is true, we call assemble_external. */
static GTY(()) tree block_move_fn;
static tree
emit_block_move_libcall_fn (for_call)
int for_call;
{
static bool emitted_extern;
tree fn = block_move_fn, args;
if (!fn)
{
if (TARGET_MEM_FUNCTIONS)
{
fn = get_identifier ("memcpy");
args = build_function_type_list (ptr_type_node, ptr_type_node,
const_ptr_type_node, sizetype,
NULL_TREE);
}
else
{
fn = get_identifier ("bcopy");
args = build_function_type_list (void_type_node, const_ptr_type_node,
ptr_type_node, unsigned_type_node,
NULL_TREE);
}
fn = build_decl (FUNCTION_DECL, fn, args);
DECL_EXTERNAL (fn) = 1;
TREE_PUBLIC (fn) = 1;
DECL_ARTIFICIAL (fn) = 1;
TREE_NOTHROW (fn) = 1;
block_move_fn = fn;
}
if (for_call && !emitted_extern)
{
emitted_extern = true;
make_decl_rtl (fn, NULL);
assemble_external (fn);
}
return fn;
}
/* A subroutine of emit_block_move. Copy the data via an explicit
loop. This is used only when libcalls are forbidden. */
/* ??? It'd be nice to copy in hunks larger than QImode. */
static void
emit_block_move_via_loop (x, y, size, align)
rtx x, y, size;
unsigned int align ATTRIBUTE_UNUSED;
{
rtx cmp_label, top_label, iter, x_addr, y_addr, tmp;
enum machine_mode iter_mode;
iter_mode = GET_MODE (size);
if (iter_mode == VOIDmode)
iter_mode = word_mode;
top_label = gen_label_rtx ();
cmp_label = gen_label_rtx ();
iter = gen_reg_rtx (iter_mode);
emit_move_insn (iter, const0_rtx);
x_addr = force_operand (XEXP (x, 0), NULL_RTX);
y_addr = force_operand (XEXP (y, 0), NULL_RTX);
do_pending_stack_adjust ();
emit_note (NULL, NOTE_INSN_LOOP_BEG);
emit_jump (cmp_label);
emit_label (top_label);
tmp = convert_modes (Pmode, iter_mode, iter, true);
x_addr = gen_rtx_PLUS (Pmode, x_addr, tmp);
y_addr = gen_rtx_PLUS (Pmode, y_addr, tmp);
x = change_address (x, QImode, x_addr);
y = change_address (y, QImode, y_addr);
emit_move_insn (x, y);
tmp = expand_simple_binop (iter_mode, PLUS, iter, const1_rtx, iter,
true, OPTAB_LIB_WIDEN);
if (tmp != iter)
emit_move_insn (iter, tmp);
emit_note (NULL, NOTE_INSN_LOOP_CONT);
emit_label (cmp_label);
emit_cmp_and_jump_insns (iter, size, LT, NULL_RTX, iter_mode,
true, top_label);
emit_note (NULL, NOTE_INSN_LOOP_END);
}
/* Copy all or part of a value X into registers starting at REGNO.
The number of registers to be filled is NREGS. */
void
move_block_to_reg (regno, x, nregs, mode)
int regno;
rtx x;
int nregs;
enum machine_mode mode;
{
int i;
#ifdef HAVE_load_multiple
rtx pat;
rtx last;
#endif
if (nregs == 0)
return;
if (CONSTANT_P (x) && ! LEGITIMATE_CONSTANT_P (x))
x = validize_mem (force_const_mem (mode, x));
/* See if the machine can do this with a load multiple insn. */
#ifdef HAVE_load_multiple
if (HAVE_load_multiple)
{
last = get_last_insn ();
pat = gen_load_multiple (gen_rtx_REG (word_mode, regno), x,
GEN_INT (nregs));
if (pat)
{
emit_insn (pat);
return;
}
else
delete_insns_since (last);
}
#endif
for (i = 0; i < nregs; i++)
emit_move_insn (gen_rtx_REG (word_mode, regno + i),
operand_subword_force (x, i, mode));
}
/* Copy all or part of a BLKmode value X out of registers starting at REGNO.
The number of registers to be filled is NREGS. SIZE indicates the number
of bytes in the object X. */
void
move_block_from_reg (regno, x, nregs, size)
int regno;
rtx x;
int nregs;
int size;
{
int i;
#ifdef HAVE_store_multiple
rtx pat;
rtx last;
#endif
enum machine_mode mode;
if (nregs == 0)
return;
/* If SIZE is that of a mode no bigger than a word, just use that
mode's store operation. */
if (size <= UNITS_PER_WORD
&& (mode = mode_for_size (size * BITS_PER_UNIT, MODE_INT, 0)) != BLKmode)
{
emit_move_insn (adjust_address (x, mode, 0), gen_rtx_REG (mode, regno));
return;
}
/* Blocks smaller than a word on a BYTES_BIG_ENDIAN machine must be aligned
to the left before storing to memory. Note that the previous test
doesn't handle all cases (e.g. SIZE == 3). */
if (size < UNITS_PER_WORD && BYTES_BIG_ENDIAN)
{
rtx tem = operand_subword (x, 0, 1, BLKmode);
rtx shift;
if (tem == 0)
abort ();
shift = expand_shift (LSHIFT_EXPR, word_mode,
gen_rtx_REG (word_mode, regno),
build_int_2 ((UNITS_PER_WORD - size)
* BITS_PER_UNIT, 0), NULL_RTX, 0);
emit_move_insn (tem, shift);
return;
}
/* See if the machine can do this with a store multiple insn. */
#ifdef HAVE_store_multiple
if (HAVE_store_multiple)
{
last = get_last_insn ();
pat = gen_store_multiple (x, gen_rtx_REG (word_mode, regno),
GEN_INT (nregs));
if (pat)
{
emit_insn (pat);
return;
}
else
delete_insns_since (last);
}
#endif
for (i = 0; i < nregs; i++)
{
rtx tem = operand_subword (x, i, 1, BLKmode);
if (tem == 0)
abort ();
emit_move_insn (tem, gen_rtx_REG (word_mode, regno + i));
}
}
/* Generate a PARALLEL rtx for a new non-consecutive group of registers from
ORIG, where ORIG is a non-consecutive group of registers represented by
a PARALLEL. The clone is identical to the original except in that the
original set of registers is replaced by a new set of pseudo registers.
The new set has the same modes as the original set. */
rtx
gen_group_rtx (orig)
rtx orig;
{
int i, length;
rtx *tmps;
if (GET_CODE (orig) != PARALLEL)
abort ();
length = XVECLEN (orig, 0);
tmps = (rtx *) alloca (sizeof (rtx) * length);
/* Skip a NULL entry in first slot. */
i = XEXP (XVECEXP (orig, 0, 0), 0) ? 0 : 1;
if (i)
tmps[0] = 0;
for (; i < length; i++)
{
enum machine_mode mode = GET_MODE (XEXP (XVECEXP (orig, 0, i), 0));
rtx offset = XEXP (XVECEXP (orig, 0, i), 1);
tmps[i] = gen_rtx_EXPR_LIST (VOIDmode, gen_reg_rtx (mode), offset);
}
return gen_rtx_PARALLEL (GET_MODE (orig), gen_rtvec_v (length, tmps));
}
/* Emit code to move a block SRC to a block DST, where DST is non-consecutive
registers represented by a PARALLEL. SSIZE represents the total size of
block SRC in bytes, or -1 if not known. */
/* ??? If SSIZE % UNITS_PER_WORD != 0, we make the blatant assumption that
the balance will be in what would be the low-order memory addresses, i.e.
left justified for big endian, right justified for little endian. This
happens to be true for the targets currently using this support. If this
ever changes, a new target macro along the lines of FUNCTION_ARG_PADDING
would be needed. */
void
emit_group_load (dst, orig_src, ssize)
rtx dst, orig_src;
int ssize;
{
rtx *tmps, src;
int start, i;
if (GET_CODE (dst) != PARALLEL)
abort ();
/* Check for a NULL entry, used to indicate that the parameter goes
both on the stack and in registers. */
if (XEXP (XVECEXP (dst, 0, 0), 0))
start = 0;
else
start = 1;
tmps = (rtx *) alloca (sizeof (rtx) * XVECLEN (dst, 0));
/* Process the pieces. */
for (i = start; i < XVECLEN (dst, 0); i++)
{
enum machine_mode mode = GET_MODE (XEXP (XVECEXP (dst, 0, i), 0));
HOST_WIDE_INT bytepos = INTVAL (XEXP (XVECEXP (dst, 0, i), 1));
unsigned int bytelen = GET_MODE_SIZE (mode);
int shift = 0;
/* Handle trailing fragments that run over the size of the struct. */
if (ssize >= 0 && bytepos + (HOST_WIDE_INT) bytelen > ssize)
{
shift = (bytelen - (ssize - bytepos)) * BITS_PER_UNIT;
bytelen = ssize - bytepos;
if (bytelen <= 0)
abort ();
}
/* If we won't be loading directly from memory, protect the real source
from strange tricks we might play; but make sure that the source can
be loaded directly into the destination. */
src = orig_src;
if (GET_CODE (orig_src) != MEM
&& (!CONSTANT_P (orig_src)
|| (GET_MODE (orig_src) != mode
&& GET_MODE (orig_src) != VOIDmode)))
{
if (GET_MODE (orig_src) == VOIDmode)
src = gen_reg_rtx (mode);
else
src = gen_reg_rtx (GET_MODE (orig_src));
emit_move_insn (src, orig_src);
}
/* Optimize the access just a bit. */
if (GET_CODE (src) == MEM
&& MEM_ALIGN (src) >= GET_MODE_ALIGNMENT (mode)
&& bytepos * BITS_PER_UNIT % GET_MODE_ALIGNMENT (mode) == 0
&& bytelen == GET_MODE_SIZE (mode))
{
tmps[i] = gen_reg_rtx (mode);
emit_move_insn (tmps[i], adjust_address (src, mode, bytepos));
}
else if (GET_CODE (src) == CONCAT)
{
unsigned int slen = GET_MODE_SIZE (GET_MODE (src));
unsigned int slen0 = GET_MODE_SIZE (GET_MODE (XEXP (src, 0)));
if ((bytepos == 0 && bytelen == slen0)
|| (bytepos != 0 && bytepos + bytelen <= slen))
{
/* The following assumes that the concatenated objects all
have the same size. In this case, a simple calculation
can be used to determine the object and the bit field
to be extracted. */
tmps[i] = XEXP (src, bytepos / slen0);
if (! CONSTANT_P (tmps[i])
&& (GET_CODE (tmps[i]) != REG || GET_MODE (tmps[i]) != mode))
tmps[i] = extract_bit_field (tmps[i], bytelen * BITS_PER_UNIT,
(bytepos % slen0) * BITS_PER_UNIT,
1, NULL_RTX, mode, mode, ssize);
}
else if (bytepos == 0)
{
rtx mem = assign_stack_temp (GET_MODE (src), slen, 0);
emit_move_insn (mem, src);
tmps[i] = adjust_address (mem, mode, 0);
}
else
abort ();
}
else if (CONSTANT_P (src)
|| (GET_CODE (src) == REG && GET_MODE (src) == mode))
tmps[i] = src;
else
tmps[i] = extract_bit_field (src, bytelen * BITS_PER_UNIT,
bytepos * BITS_PER_UNIT, 1, NULL_RTX,
mode, mode, ssize);
if (BYTES_BIG_ENDIAN && shift)
expand_binop (mode, ashl_optab, tmps[i], GEN_INT (shift),
tmps[i], 0, OPTAB_WIDEN);
}
emit_queue ();
/* Copy the extracted pieces into the proper (probable) hard regs. */
for (i = start; i < XVECLEN (dst, 0); i++)
emit_move_insn (XEXP (XVECEXP (dst, 0, i), 0), tmps[i]);
}
/* Emit code to move a block SRC to block DST, where SRC and DST are
non-consecutive groups of registers, each represented by a PARALLEL. */
void
emit_group_move (dst, src)
rtx dst, src;
{
int i;
if (GET_CODE (src) != PARALLEL
|| GET_CODE (dst) != PARALLEL
|| XVECLEN (src, 0) != XVECLEN (dst, 0))
abort ();
/* Skip first entry if NULL. */
for (i = XEXP (XVECEXP (src, 0, 0), 0) ? 0 : 1; i < XVECLEN (src, 0); i++)
emit_move_insn (XEXP (XVECEXP (dst, 0, i), 0),
XEXP (XVECEXP (src, 0, i), 0));
}
/* Emit code to move a block SRC to a block DST, where SRC is non-consecutive
registers represented by a PARALLEL. SSIZE represents the total size of
block DST, or -1 if not known. */
void
emit_group_store (orig_dst, src, ssize)
rtx orig_dst, src;
int ssize;
{
rtx *tmps, dst;
int start, i;
if (GET_CODE (src) != PARALLEL)
abort ();
/* Check for a NULL entry, used to indicate that the parameter goes
both on the stack and in registers. */
if (XEXP (XVECEXP (src, 0, 0), 0))
start = 0;
else
start = 1;
tmps = (rtx *) alloca (sizeof (rtx) * XVECLEN (src, 0));
/* Copy the (probable) hard regs into pseudos. */
for (i = start; i < XVECLEN (src, 0); i++)
{
rtx reg = XEXP (XVECEXP (src, 0, i), 0);
tmps[i] = gen_reg_rtx (GET_MODE (reg));
emit_move_insn (tmps[i], reg);
}
emit_queue ();
/* If we won't be storing directly into memory, protect the real destination
from strange tricks we might play. */
dst = orig_dst;
if (GET_CODE (dst) == PARALLEL)
{
rtx temp;
/* We can get a PARALLEL dst if there is a conditional expression in
a return statement. In that case, the dst and src are the same,
so no action is necessary. */
if (rtx_equal_p (dst, src))
return;
/* It is unclear if we can ever reach here, but we may as well handle
it. Allocate a temporary, and split this into a store/load to/from
the temporary. */
temp = assign_stack_temp (GET_MODE (dst), ssize, 0);
emit_group_store (temp, src, ssize);
emit_group_load (dst, temp, ssize);
return;
}
else if (GET_CODE (dst) != MEM && GET_CODE (dst) != CONCAT)
{
dst = gen_reg_rtx (GET_MODE (orig_dst));
/* Make life a bit easier for combine. */
emit_move_insn (dst, CONST0_RTX (GET_MODE (orig_dst)));
}
/* Process the pieces. */
for (i = start; i < XVECLEN (src, 0); i++)
{
HOST_WIDE_INT bytepos = INTVAL (XEXP (XVECEXP (src, 0, i), 1));
enum machine_mode mode = GET_MODE (tmps[i]);
unsigned int bytelen = GET_MODE_SIZE (mode);
rtx dest = dst;
/* Handle trailing fragments that run over the size of the struct. */
if (ssize >= 0 && bytepos + (HOST_WIDE_INT) bytelen > ssize)
{
if (BYTES_BIG_ENDIAN)
{
int shift = (bytelen - (ssize - bytepos)) * BITS_PER_UNIT;
expand_binop (mode, ashr_optab, tmps[i], GEN_INT (shift),
tmps[i], 0, OPTAB_WIDEN);
}
bytelen = ssize - bytepos;
}
if (GET_CODE (dst) == CONCAT)
{
if (bytepos + bytelen <= GET_MODE_SIZE (GET_MODE (XEXP (dst, 0))))
dest = XEXP (dst, 0);
else if (bytepos >= GET_MODE_SIZE (GET_MODE (XEXP (dst, 0))))
{
bytepos -= GET_MODE_SIZE (GET_MODE (XEXP (dst, 0)));
dest = XEXP (dst, 1);
}
else if (bytepos == 0 && XVECLEN (src, 0))
{
dest = assign_stack_temp (GET_MODE (dest),
GET_MODE_SIZE (GET_MODE (dest)), 0);
emit_move_insn (adjust_address (dest, GET_MODE (tmps[i]), bytepos),
tmps[i]);
dst = dest;
break;
}
else
abort ();
}
/* Optimize the access just a bit. */
if (GET_CODE (dest) == MEM
&& MEM_ALIGN (dest) >= GET_MODE_ALIGNMENT (mode)
&& bytepos * BITS_PER_UNIT % GET_MODE_ALIGNMENT (mode) == 0
&& bytelen == GET_MODE_SIZE (mode))
emit_move_insn (adjust_address (dest, mode, bytepos), tmps[i]);
else
store_bit_field (dest, bytelen * BITS_PER_UNIT, bytepos * BITS_PER_UNIT,
mode, tmps[i], ssize);
}
emit_queue ();
/* Copy from the pseudo into the (probable) hard reg. */
if (orig_dst != dst)
emit_move_insn (orig_dst, dst);
}
/* Generate code to copy a BLKmode object of TYPE out of a
set of registers starting with SRCREG into TGTBLK. If TGTBLK
is null, a stack temporary is created. TGTBLK is returned.
The primary purpose of this routine is to handle functions
that return BLKmode structures in registers. Some machines
(the PA for example) want to return all small structures
in registers regardless of the structure's alignment. */
rtx
copy_blkmode_from_reg (tgtblk, srcreg, type)
rtx tgtblk;
rtx srcreg;
tree type;
{
unsigned HOST_WIDE_INT bytes = int_size_in_bytes (type);
rtx src = NULL, dst = NULL;
unsigned HOST_WIDE_INT bitsize = MIN (TYPE_ALIGN (type), BITS_PER_WORD);
unsigned HOST_WIDE_INT bitpos, xbitpos, big_endian_correction = 0;
if (tgtblk == 0)
{
tgtblk = assign_temp (build_qualified_type (type,
(TYPE_QUALS (type)
| TYPE_QUAL_CONST)),
0, 1, 1);
preserve_temp_slots (tgtblk);
}
/* This code assumes srcreg is at least a full word. If it isn't, copy it
into a new pseudo which is a full word. */
if (GET_MODE (srcreg) != BLKmode
&& GET_MODE_SIZE (GET_MODE (srcreg)) < UNITS_PER_WORD)
srcreg = convert_to_mode (word_mode, srcreg, TREE_UNSIGNED (type));
/* Structures whose size is not a multiple of a word are aligned
to the least significant byte (to the right). On a BYTES_BIG_ENDIAN
machine, this means we must skip the empty high order bytes when
calculating the bit offset. */
if (BYTES_BIG_ENDIAN
&& bytes % UNITS_PER_WORD)
big_endian_correction
= (BITS_PER_WORD - ((bytes % UNITS_PER_WORD) * BITS_PER_UNIT));
/* Copy the structure BITSIZE bites at a time.
We could probably emit more efficient code for machines which do not use
strict alignment, but it doesn't seem worth the effort at the current
time. */
for (bitpos = 0, xbitpos = big_endian_correction;
bitpos < bytes * BITS_PER_UNIT;
bitpos += bitsize, xbitpos += bitsize)
{
/* We need a new source operand each time xbitpos is on a
word boundary and when xbitpos == big_endian_correction
(the first time through). */
if (xbitpos % BITS_PER_WORD == 0
|| xbitpos == big_endian_correction)
src = operand_subword_force (srcreg, xbitpos / BITS_PER_WORD,
GET_MODE (srcreg));
/* We need a new destination operand each time bitpos is on
a word boundary. */
if (bitpos % BITS_PER_WORD == 0)
dst = operand_subword (tgtblk, bitpos / BITS_PER_WORD, 1, BLKmode);
/* Use xbitpos for the source extraction (right justified) and
xbitpos for the destination store (left justified). */
store_bit_field (dst, bitsize, bitpos % BITS_PER_WORD, word_mode,
extract_bit_field (src, bitsize,
xbitpos % BITS_PER_WORD, 1,
NULL_RTX, word_mode, word_mode,
BITS_PER_WORD),
BITS_PER_WORD);
}
return tgtblk;
}
/* Add a USE expression for REG to the (possibly empty) list pointed
to by CALL_FUSAGE. REG must denote a hard register. */
void
use_reg (call_fusage, reg)
rtx *call_fusage, reg;
{
if (GET_CODE (reg) != REG
|| REGNO (reg) >= FIRST_PSEUDO_REGISTER)
abort ();
*call_fusage
= gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_USE (VOIDmode, reg), *call_fusage);
}
/* Add USE expressions to *CALL_FUSAGE for each of NREGS consecutive regs,
starting at REGNO. All of these registers must be hard registers. */
void
use_regs (call_fusage, regno, nregs)
rtx *call_fusage;
int regno;
int nregs;
{
int i;
if (regno + nregs > FIRST_PSEUDO_REGISTER)
abort ();
for (i = 0; i < nregs; i++)
use_reg (call_fusage, regno_reg_rtx[regno + i]);
}
/* Add USE expressions to *CALL_FUSAGE for each REG contained in the
PARALLEL REGS. This is for calls that pass values in multiple
non-contiguous locations. The Irix 6 ABI has examples of this. */
void
use_group_regs (call_fusage, regs)
rtx *call_fusage;
rtx regs;
{
int i;
for (i = 0; i < XVECLEN (regs, 0); i++)
{
rtx reg = XEXP (XVECEXP (regs, 0, i), 0);
/* A NULL entry means the parameter goes both on the stack and in
registers. This can also be a MEM for targets that pass values
partially on the stack and partially in registers. */
if (reg != 0 && GET_CODE (reg) == REG)
use_reg (call_fusage, reg);
}
}
/* Determine whether the LEN bytes generated by CONSTFUN can be
stored to memory using several move instructions. CONSTFUNDATA is
a pointer which will be passed as argument in every CONSTFUN call.
ALIGN is maximum alignment we can assume. Return nonzero if a
call to store_by_pieces should succeed. */
int
can_store_by_pieces (len, constfun, constfundata, align)
unsigned HOST_WIDE_INT len;
rtx (*constfun) PARAMS ((PTR, HOST_WIDE_INT, enum machine_mode));
PTR constfundata;
unsigned int align;
{
unsigned HOST_WIDE_INT max_size, l;
HOST_WIDE_INT offset = 0;
enum machine_mode mode, tmode;
enum insn_code icode;
int reverse;
rtx cst;
if (len == 0)
return 1;
if (! MOVE_BY_PIECES_P (len, align))
return 0;
if (! SLOW_UNALIGNED_ACCESS (word_mode, align)
|| align > MOVE_MAX * BITS_PER_UNIT || align >= BIGGEST_ALIGNMENT)
align = MOVE_MAX * BITS_PER_UNIT;
/* We would first store what we can in the largest integer mode, then go to
successively smaller modes. */
for (reverse = 0;
reverse <= (HAVE_PRE_DECREMENT || HAVE_POST_DECREMENT);
reverse++)
{
l = len;
mode = VOIDmode;
max_size = STORE_MAX_PIECES + 1;
while (max_size > 1)
{
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (mode == VOIDmode)
break;
icode = mov_optab->handlers[(int) mode].insn_code;
if (icode != CODE_FOR_nothing
&& align >= GET_MODE_ALIGNMENT (mode))
{
unsigned int size = GET_MODE_SIZE (mode);
while (l >= size)
{
if (reverse)
offset -= size;
cst = (*constfun) (constfundata, offset, mode);
if (!LEGITIMATE_CONSTANT_P (cst))
return 0;
if (!reverse)
offset += size;
l -= size;
}
}
max_size = GET_MODE_SIZE (mode);
}
/* The code above should have handled everything. */
if (l != 0)
abort ();
}
return 1;
}
/* Generate several move instructions to store LEN bytes generated by
CONSTFUN to block TO. (A MEM rtx with BLKmode). CONSTFUNDATA is a
pointer which will be passed as argument in every CONSTFUN call.
ALIGN is maximum alignment we can assume. */
void
store_by_pieces (to, len, constfun, constfundata, align)
rtx to;
unsigned HOST_WIDE_INT len;
rtx (*constfun) PARAMS ((PTR, HOST_WIDE_INT, enum machine_mode));
PTR constfundata;
unsigned int align;
{
struct store_by_pieces data;
if (len == 0)
return;
if (! MOVE_BY_PIECES_P (len, align))
abort ();
to = protect_from_queue (to, 1);
data.constfun = constfun;
data.constfundata = constfundata;
data.len = len;
data.to = to;
store_by_pieces_1 (&data, align);
}
/* Generate several move instructions to clear LEN bytes of block TO. (A MEM
rtx with BLKmode). The caller must pass TO through protect_from_queue
before calling. ALIGN is maximum alignment we can assume. */
static void
clear_by_pieces (to, len, align)
rtx to;
unsigned HOST_WIDE_INT len;
unsigned int align;
{
struct store_by_pieces data;
if (len == 0)
return;
data.constfun = clear_by_pieces_1;
data.constfundata = NULL;
data.len = len;
data.to = to;
store_by_pieces_1 (&data, align);
}
/* Callback routine for clear_by_pieces.
Return const0_rtx unconditionally. */
static rtx
clear_by_pieces_1 (data, offset, mode)
PTR data ATTRIBUTE_UNUSED;
HOST_WIDE_INT offset ATTRIBUTE_UNUSED;
enum machine_mode mode ATTRIBUTE_UNUSED;
{
return const0_rtx;
}
/* Subroutine of clear_by_pieces and store_by_pieces.
Generate several move instructions to store LEN bytes of block TO. (A MEM
rtx with BLKmode). The caller must pass TO through protect_from_queue
before calling. ALIGN is maximum alignment we can assume. */
static void
store_by_pieces_1 (data, align)
struct store_by_pieces *data;
unsigned int align;
{
rtx to_addr = XEXP (data->to, 0);
unsigned HOST_WIDE_INT max_size = STORE_MAX_PIECES + 1;
enum machine_mode mode = VOIDmode, tmode;
enum insn_code icode;
data->offset = 0;
data->to_addr = to_addr;
data->autinc_to
= (GET_CODE (to_addr) == PRE_INC || GET_CODE (to_addr) == PRE_DEC
|| GET_CODE (to_addr) == POST_INC || GET_CODE (to_addr) == POST_DEC);
data->explicit_inc_to = 0;
data->reverse
= (GET_CODE (to_addr) == PRE_DEC || GET_CODE (to_addr) == POST_DEC);
if (data->reverse)
data->offset = data->len;
/* If storing requires more than two move insns,
copy addresses to registers (to make displacements shorter)
and use post-increment if available. */
if (!data->autinc_to
&& move_by_pieces_ninsns (data->len, align) > 2)
{
/* Determine the main mode we'll be using. */
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (USE_STORE_PRE_DECREMENT (mode) && data->reverse && ! data->autinc_to)
{
data->to_addr = copy_addr_to_reg (plus_constant (to_addr, data->len));
data->autinc_to = 1;
data->explicit_inc_to = -1;
}
if (USE_STORE_POST_INCREMENT (mode) && ! data->reverse
&& ! data->autinc_to)
{
data->to_addr = copy_addr_to_reg (to_addr);
data->autinc_to = 1;
data->explicit_inc_to = 1;
}
if ( !data->autinc_to && CONSTANT_P (to_addr))
data->to_addr = copy_addr_to_reg (to_addr);
}
if (! SLOW_UNALIGNED_ACCESS (word_mode, align)
|| align > MOVE_MAX * BITS_PER_UNIT || align >= BIGGEST_ALIGNMENT)
align = MOVE_MAX * BITS_PER_UNIT;
/* First store what we can in the largest integer mode, then go to
successively smaller modes. */
while (max_size > 1)
{
for (tmode = GET_CLASS_NARROWEST_MODE (MODE_INT);
tmode != VOIDmode; tmode = GET_MODE_WIDER_MODE (tmode))
if (GET_MODE_SIZE (tmode) < max_size)
mode = tmode;
if (mode == VOIDmode)
break;
icode = mov_optab->handlers[(int) mode].insn_code;
if (icode != CODE_FOR_nothing && align >= GET_MODE_ALIGNMENT (mode))
store_by_pieces_2 (GEN_FCN (icode), mode, data);
max_size = GET_MODE_SIZE (mode);
}
/* The code above should have handled everything. */
if (data->len != 0)
abort ();
}
/* Subroutine of store_by_pieces_1. Store as many bytes as appropriate
with move instructions for mode MODE. GENFUN is the gen_... function
to make a move insn for that mode. DATA has all the other info. */
static void
store_by_pieces_2 (genfun, mode, data)
rtx (*genfun) PARAMS ((rtx, ...));
enum machine_mode mode;
struct store_by_pieces *data;
{
unsigned int size = GET_MODE_SIZE (mode);
rtx to1, cst;
while (data->len >= size)
{
if (data->reverse)
data->offset -= size;
if (data->autinc_to)
to1 = adjust_automodify_address (data->to, mode, data->to_addr,
data->offset);
else
to1 = adjust_address (data->to, mode, data->offset);
if (HAVE_PRE_DECREMENT && data->explicit_inc_to < 0)
emit_insn (gen_add2_insn (data->to_addr,
GEN_INT (-(HOST_WIDE_INT) size)));
cst = (*data->constfun) (data->constfundata, data->offset, mode);
emit_insn ((*genfun) (to1, cst));
if (HAVE_POST_INCREMENT && data->explicit_inc_to > 0)
emit_insn (gen_add2_insn (data->to_addr, GEN_INT (size)));
if (! data->reverse)
data->offset += size;
data->len -= size;
}
}
/* Write zeros through the storage of OBJECT. If OBJECT has BLKmode, SIZE is
its length in bytes. */
rtx
clear_storage (object, size)
rtx object;
rtx size;
{
rtx retval = 0;
unsigned int align = (GET_CODE (object) == MEM ? MEM_ALIGN (object)
: GET_MODE_ALIGNMENT (GET_MODE (object)));
/* If OBJECT is not BLKmode and SIZE is the same size as its mode,
just move a zero. Otherwise, do this a piece at a time. */
if (GET_MODE (object) != BLKmode
&& GET_CODE (size) == CONST_INT
&& INTVAL (size) == (HOST_WIDE_INT) GET_MODE_SIZE (GET_MODE (object)))
emit_move_insn (object, CONST0_RTX (GET_MODE (object)));
else
{
object = protect_from_queue (object, 1);
size = protect_from_queue (size, 0);
if (GET_CODE (size) == CONST_INT && INTVAL (size) == 0)
;
else if (GET_CODE (size) == CONST_INT
&& CLEAR_BY_PIECES_P (INTVAL (size), align))
clear_by_pieces (object, INTVAL (size), align);
else if (clear_storage_via_clrstr (object, size, align))
;
else
retval = clear_storage_via_libcall (object, size);
}
return retval;
}
/* A subroutine of clear_storage. Expand a clrstr pattern;
return true if successful. */
static bool
clear_storage_via_clrstr (object, size, align)
rtx object, size;
unsigned int align;
{
/* Try the most limited insn first, because there's no point
including more than one in the machine description unless
the more limited one has some advantage. */
rtx opalign = GEN_INT (align / BITS_PER_UNIT);
enum machine_mode mode;
for (mode = GET_CLASS_NARROWEST_MODE (MODE_INT); mode != VOIDmode;
mode = GET_MODE_WIDER_MODE (mode))
{
enum insn_code code = clrstr_optab[(int) mode];
insn_operand_predicate_fn pred;
if (code != CODE_FOR_nothing
/* We don't need MODE to be narrower than
BITS_PER_HOST_WIDE_INT here because if SIZE is less than
the mode mask, as it is returned by the macro, it will
definitely be less than the actual mode mask. */
&& ((GET_CODE (size) == CONST_INT
&& ((unsigned HOST_WIDE_INT) INTVAL (size)
<= (GET_MODE_MASK (mode) >> 1)))
|| GET_MODE_BITSIZE (mode) >= BITS_PER_WORD)
&& ((pred = insn_data[(int) code].operand[0].predicate) == 0
|| (*pred) (object, BLKmode))
&& ((pred = insn_data[(int) code].operand[2].predicate) == 0
|| (*pred) (opalign, VOIDmode)))
{
rtx op1;
rtx last = get_last_insn ();
rtx pat;
op1 = convert_to_mode (mode, size, 1);
pred = insn_data[(int) code].operand[1].predicate;
if (pred != 0 && ! (*pred) (op1, mode))
op1 = copy_to_mode_reg (mode, op1);
pat = GEN_FCN ((int) code) (object, op1, opalign);
if (pat)
{
emit_insn (pat);
return true;
}
else
delete_insns_since (last);
}
}
return false;
}
/* A subroutine of clear_storage. Expand a call to memset or bzero.
Return the return value of memset, 0 otherwise. */
static rtx
clear_storage_via_libcall (object, size)
rtx object, size;
{
tree call_expr, arg_list, fn, object_tree, size_tree;
enum machine_mode size_mode;
rtx retval;
/* OBJECT or SIZE may have been passed through protect_from_queue.
It is unsafe to save the value generated by protect_from_queue
and reuse it later. Consider what happens if emit_queue is
called before the return value from protect_from_queue is used.
Expansion of the CALL_EXPR below will call emit_queue before
we are finished emitting RTL for argument setup. So if we are
not careful we could get the wrong value for an argument.
To avoid this problem we go ahead and emit code to copy OBJECT
and SIZE into new pseudos. We can then place those new pseudos
into an RTL_EXPR and use them later, even after a call to
emit_queue.
Note this is not strictly needed for library calls since they
do not call emit_queue before loading their arguments. However,
we may need to have library calls call emit_queue in the future
since failing to do so could cause problems for targets which
define SMALL_REGISTER_CLASSES and pass arguments in registers. */
object = copy_to_mode_reg (Pmode, XEXP (object, 0));
if (TARGET_MEM_FUNCTIONS)
size_mode = TYPE_MODE (sizetype);
else
size_mode = TYPE_MODE (unsigned_type_node);
size = convert_to_mode (size_mode, size, 1);
size = copy_to_mode_reg (size_mode, size);
/* It is incorrect to use the libcall calling conventions to call
memset in this context. This could be a user call to memset and
the user may wish to examine the return value from memset. For
targets where libcalls and normal calls have different conventions
for returning pointers, we could end up generating incorrect code.
For convenience, we generate the call to bzero this way as well. */
object_tree = make_tree (ptr_type_node, object);
if (TARGET_MEM_FUNCTIONS)
size_tree = make_tree (sizetype, size);
else
size_tree = make_tree (unsigned_type_node, size);
fn = clear_storage_libcall_fn (true);
arg_list = tree_cons (NULL_TREE, size_tree, NULL_TREE);
if (TARGET_MEM_FUNCTIONS)
arg_list = tree_cons (NULL_TREE, integer_zero_node, arg_list);
arg_list = tree_cons (NULL_TREE, object_tree, arg_list);
/* Now we have to build up the CALL_EXPR itself. */
call_expr = build1 (ADDR_EXPR, build_pointer_type (TREE_TYPE (fn)), fn);
call_expr = build (CALL_EXPR, TREE_TYPE (TREE_TYPE (fn)),
call_expr, arg_list, NULL_TREE);
TREE_SIDE_EFFECTS (call_expr) = 1;
retval = expand_expr (call_expr, NULL_RTX, VOIDmode, 0);
/* If we are initializing a readonly value, show the above call
clobbered it. Otherwise, a load from it may erroneously be
hoisted from a loop. */
if (RTX_UNCHANGING_P (object))
emit_insn (gen_rtx_CLOBBER (VOIDmode, object));
return (TARGET_MEM_FUNCTIONS ? retval : NULL_RTX);
}
/* A subroutine of clear_storage_via_libcall. Create the tree node
for the function we use for block clears. The first time FOR_CALL
is true, we call assemble_external. */
static GTY(()) tree block_clear_fn;
static tree
clear_storage_libcall_fn (for_call)
int for_call;
{
static bool emitted_extern;
tree fn = block_clear_fn, args;
if (!fn)
{
if (TARGET_MEM_FUNCTIONS)
{
fn = get_identifier ("memset");
args = build_function_type_list (ptr_type_node, ptr_type_node,
integer_type_node, sizetype,
NULL_TREE);
}
else
{
fn = get_identifier ("bzero");
args = build_function_type_list (void_type_node, ptr_type_node,
unsigned_type_node, NULL_TREE);
}