mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-23 11:18:54 +00:00
3485 lines
112 KiB
C
3485 lines
112 KiB
C
/* Procedure integration for GNU CC.
|
||
Copyright (C) 1988, 91, 93-98, 1999 Free Software Foundation, Inc.
|
||
Contributed by Michael Tiemann (tiemann@cygnus.com)
|
||
|
||
This file is part of GNU CC.
|
||
|
||
GNU CC 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.
|
||
|
||
GNU CC 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 GNU CC; 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 "rtl.h"
|
||
#include "tree.h"
|
||
#include "regs.h"
|
||
#include "flags.h"
|
||
#include "insn-config.h"
|
||
#include "insn-flags.h"
|
||
#include "expr.h"
|
||
#include "output.h"
|
||
#include "recog.h"
|
||
#include "integrate.h"
|
||
#include "real.h"
|
||
#include "except.h"
|
||
#include "function.h"
|
||
#include "toplev.h"
|
||
#include "intl.h"
|
||
|
||
#include "obstack.h"
|
||
#define obstack_chunk_alloc xmalloc
|
||
#define obstack_chunk_free free
|
||
|
||
extern struct obstack *function_maybepermanent_obstack;
|
||
|
||
/* Similar, but round to the next highest integer that meets the
|
||
alignment. */
|
||
#define CEIL_ROUND(VALUE,ALIGN) (((VALUE) + (ALIGN) - 1) & ~((ALIGN)- 1))
|
||
|
||
/* Default max number of insns a function can have and still be inline.
|
||
This is overridden on RISC machines. */
|
||
#ifndef INTEGRATE_THRESHOLD
|
||
/* Inlining small functions might save more space then not inlining at
|
||
all. Assume 1 instruction for the call and 1.5 insns per argument. */
|
||
#define INTEGRATE_THRESHOLD(DECL) \
|
||
(optimize_size \
|
||
? (1 + (3 * list_length (DECL_ARGUMENTS (DECL))) / 2) \
|
||
: (8 * (8 + list_length (DECL_ARGUMENTS (DECL)))))
|
||
#endif
|
||
|
||
static rtx initialize_for_inline PROTO((tree, int, int, int, int));
|
||
static void finish_inline PROTO((tree, rtx));
|
||
static void adjust_copied_decl_tree PROTO((tree));
|
||
static tree copy_decl_list PROTO((tree));
|
||
static tree copy_decl_tree PROTO((tree));
|
||
static void copy_decl_rtls PROTO((tree));
|
||
static void save_constants PROTO((rtx *));
|
||
static void note_modified_parmregs PROTO((rtx, rtx));
|
||
static rtx copy_for_inline PROTO((rtx));
|
||
static void integrate_parm_decls PROTO((tree, struct inline_remap *,
|
||
rtvec));
|
||
static void integrate_decl_tree PROTO((tree, int,
|
||
struct inline_remap *));
|
||
static void save_constants_in_decl_trees PROTO ((tree));
|
||
static void subst_constants PROTO((rtx *, rtx,
|
||
struct inline_remap *));
|
||
static void restore_constants PROTO((rtx *));
|
||
static void set_block_origin_self PROTO((tree));
|
||
static void set_decl_origin_self PROTO((tree));
|
||
static void set_block_abstract_flags PROTO((tree, int));
|
||
static void process_reg_param PROTO((struct inline_remap *, rtx,
|
||
rtx));
|
||
|
||
|
||
void set_decl_abstract_flags PROTO((tree, int));
|
||
static tree copy_and_set_decl_abstract_origin PROTO((tree));
|
||
|
||
/* The maximum number of instructions accepted for inlining a
|
||
function. Increasing values mean more agressive inlining.
|
||
This affects currently only functions explicitly marked as
|
||
inline (or methods defined within the class definition for C++).
|
||
The default value of 10000 is arbitrary but high to match the
|
||
previously unlimited gcc capabilities. */
|
||
|
||
int inline_max_insns = 10000;
|
||
|
||
|
||
/* Returns the Ith entry in the label_map contained in MAP. If the
|
||
Ith entry has not yet been set, return a fresh label. This function
|
||
performs a lazy initialization of label_map, thereby avoiding huge memory
|
||
explosions when the label_map gets very large. */
|
||
|
||
rtx
|
||
get_label_from_map (map, i)
|
||
struct inline_remap *map;
|
||
int i;
|
||
{
|
||
rtx x = map->label_map[i];
|
||
|
||
if (x == NULL_RTX)
|
||
x = map->label_map[i] = gen_label_rtx();
|
||
|
||
return x;
|
||
}
|
||
|
||
/* Zero if the current function (whose FUNCTION_DECL is FNDECL)
|
||
is safe and reasonable to integrate into other functions.
|
||
Nonzero means value is a warning msgid with a single %s
|
||
for the function's name. */
|
||
|
||
const char *
|
||
function_cannot_inline_p (fndecl)
|
||
register tree fndecl;
|
||
{
|
||
register rtx insn;
|
||
tree last = tree_last (TYPE_ARG_TYPES (TREE_TYPE (fndecl)));
|
||
|
||
/* For functions marked as inline increase the maximum size to
|
||
inline_max_insns (-finline-limit-<n>). For regular functions
|
||
use the limit given by INTEGRATE_THRESHOLD. */
|
||
|
||
int max_insns = (DECL_INLINE (fndecl))
|
||
? (inline_max_insns
|
||
+ 8 * list_length (DECL_ARGUMENTS (fndecl)))
|
||
: INTEGRATE_THRESHOLD (fndecl);
|
||
|
||
register int ninsns = 0;
|
||
register tree parms;
|
||
rtx result;
|
||
|
||
/* No inlines with varargs. */
|
||
if ((last && TREE_VALUE (last) != void_type_node)
|
||
|| current_function_varargs)
|
||
return N_("varargs function cannot be inline");
|
||
|
||
if (current_function_calls_alloca)
|
||
return N_("function using alloca cannot be inline");
|
||
|
||
if (current_function_contains_functions)
|
||
return N_("function with nested functions cannot be inline");
|
||
|
||
if (current_function_cannot_inline)
|
||
return current_function_cannot_inline;
|
||
|
||
/* If its not even close, don't even look. */
|
||
if (get_max_uid () > 3 * max_insns)
|
||
return N_("function too large to be inline");
|
||
|
||
#if 0
|
||
/* Don't inline functions which do not specify a function prototype and
|
||
have BLKmode argument or take the address of a parameter. */
|
||
for (parms = DECL_ARGUMENTS (fndecl); parms; parms = TREE_CHAIN (parms))
|
||
{
|
||
if (TYPE_MODE (TREE_TYPE (parms)) == BLKmode)
|
||
TREE_ADDRESSABLE (parms) = 1;
|
||
if (last == NULL_TREE && TREE_ADDRESSABLE (parms))
|
||
return N_("no prototype, and parameter address used; cannot be inline");
|
||
}
|
||
#endif
|
||
|
||
/* We can't inline functions that return structures
|
||
the old-fashioned PCC way, copying into a static block. */
|
||
if (current_function_returns_pcc_struct)
|
||
return N_("inline functions not supported for this return value type");
|
||
|
||
/* We can't inline functions that return structures of varying size. */
|
||
if (int_size_in_bytes (TREE_TYPE (TREE_TYPE (fndecl))) < 0)
|
||
return N_("function with varying-size return value cannot be inline");
|
||
|
||
/* Cannot inline a function with a varying size argument or one that
|
||
receives a transparent union. */
|
||
for (parms = DECL_ARGUMENTS (fndecl); parms; parms = TREE_CHAIN (parms))
|
||
{
|
||
if (int_size_in_bytes (TREE_TYPE (parms)) < 0)
|
||
return N_("function with varying-size parameter cannot be inline");
|
||
else if (TYPE_TRANSPARENT_UNION (TREE_TYPE (parms)))
|
||
return N_("function with transparent unit parameter cannot be inline");
|
||
}
|
||
|
||
if (get_max_uid () > max_insns)
|
||
{
|
||
for (ninsns = 0, insn = get_first_nonparm_insn ();
|
||
insn && ninsns < max_insns;
|
||
insn = NEXT_INSN (insn))
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) == 'i')
|
||
ninsns++;
|
||
|
||
if (ninsns >= max_insns)
|
||
return N_("function too large to be inline");
|
||
}
|
||
|
||
/* We will not inline a function which uses computed goto. The addresses of
|
||
its local labels, which may be tucked into global storage, are of course
|
||
not constant across instantiations, which causes unexpected behaviour. */
|
||
if (current_function_has_computed_jump)
|
||
return N_("function with computed jump cannot inline");
|
||
|
||
/* We cannot inline a nested function that jumps to a nonlocal label. */
|
||
if (current_function_has_nonlocal_goto)
|
||
return N_("function with nonlocal goto cannot be inline");
|
||
|
||
/* This is a hack, until the inliner is taught about eh regions at
|
||
the start of the function. */
|
||
for (insn = get_insns ();
|
||
insn
|
||
&& ! (GET_CODE (insn) == NOTE
|
||
&& NOTE_LINE_NUMBER (insn) == NOTE_INSN_FUNCTION_BEG);
|
||
insn = NEXT_INSN (insn))
|
||
{
|
||
if (insn && GET_CODE (insn) == NOTE
|
||
&& NOTE_LINE_NUMBER (insn) == NOTE_INSN_EH_REGION_BEG)
|
||
return N_("function with complex parameters cannot be inline");
|
||
}
|
||
|
||
/* We can't inline functions that return a PARALLEL rtx. */
|
||
result = DECL_RTL (DECL_RESULT (fndecl));
|
||
if (result && GET_CODE (result) == PARALLEL)
|
||
return N_("inline functions not supported for this return value type");
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Variables used within save_for_inline. */
|
||
|
||
/* Mapping from old pseudo-register to new pseudo-registers.
|
||
The first element of this map is reg_map[FIRST_PSEUDO_REGISTER].
|
||
It is allocated in `save_for_inline' and `expand_inline_function',
|
||
and deallocated on exit from each of those routines. */
|
||
static rtx *reg_map;
|
||
|
||
/* Mapping from old code-labels to new code-labels.
|
||
The first element of this map is label_map[min_labelno].
|
||
It is allocated in `save_for_inline' and `expand_inline_function',
|
||
and deallocated on exit from each of those routines. */
|
||
static rtx *label_map;
|
||
|
||
/* Mapping from old insn uid's to copied insns.
|
||
It is allocated in `save_for_inline' and `expand_inline_function',
|
||
and deallocated on exit from each of those routines. */
|
||
static rtx *insn_map;
|
||
|
||
/* Map pseudo reg number into the PARM_DECL for the parm living in the reg.
|
||
Zero for a reg that isn't a parm's home.
|
||
Only reg numbers less than max_parm_reg are mapped here. */
|
||
static tree *parmdecl_map;
|
||
|
||
/* Keep track of first pseudo-register beyond those that are parms. */
|
||
extern int max_parm_reg;
|
||
extern rtx *parm_reg_stack_loc;
|
||
|
||
/* When an insn is being copied by copy_for_inline,
|
||
this is nonzero if we have copied an ASM_OPERANDS.
|
||
In that case, it is the original input-operand vector. */
|
||
static rtvec orig_asm_operands_vector;
|
||
|
||
/* When an insn is being copied by copy_for_inline,
|
||
this is nonzero if we have copied an ASM_OPERANDS.
|
||
In that case, it is the copied input-operand vector. */
|
||
static rtvec copy_asm_operands_vector;
|
||
|
||
/* Likewise, this is the copied constraints vector. */
|
||
static rtvec copy_asm_constraints_vector;
|
||
|
||
/* In save_for_inline, nonzero if past the parm-initialization insns. */
|
||
static int in_nonparm_insns;
|
||
|
||
/* subroutines passed to duplicate_eh_handlers to map exception labels */
|
||
|
||
static rtx
|
||
save_for_inline_eh_labelmap (label)
|
||
rtx label;
|
||
{
|
||
int index = CODE_LABEL_NUMBER (label);
|
||
return label_map[index];
|
||
}
|
||
|
||
/* Subroutine for `save_for_inline{copying,nocopy}'. Performs initialization
|
||
needed to save FNDECL's insns and info for future inline expansion. */
|
||
|
||
static rtx
|
||
initialize_for_inline (fndecl, min_labelno, max_labelno, max_reg, copy)
|
||
tree fndecl;
|
||
int min_labelno;
|
||
int max_labelno;
|
||
int max_reg;
|
||
int copy;
|
||
{
|
||
int function_flags, i;
|
||
rtvec arg_vector;
|
||
tree parms;
|
||
|
||
/* Compute the values of any flags we must restore when inlining this. */
|
||
|
||
function_flags
|
||
= (current_function_calls_alloca * FUNCTION_FLAGS_CALLS_ALLOCA
|
||
+ current_function_calls_setjmp * FUNCTION_FLAGS_CALLS_SETJMP
|
||
+ current_function_calls_longjmp * FUNCTION_FLAGS_CALLS_LONGJMP
|
||
+ current_function_returns_struct * FUNCTION_FLAGS_RETURNS_STRUCT
|
||
+ (current_function_returns_pcc_struct
|
||
* FUNCTION_FLAGS_RETURNS_PCC_STRUCT)
|
||
+ current_function_needs_context * FUNCTION_FLAGS_NEEDS_CONTEXT
|
||
+ (current_function_has_nonlocal_label
|
||
* FUNCTION_FLAGS_HAS_NONLOCAL_LABEL)
|
||
+ current_function_returns_pointer * FUNCTION_FLAGS_RETURNS_POINTER
|
||
+ current_function_uses_const_pool * FUNCTION_FLAGS_USES_CONST_POOL
|
||
+ (current_function_uses_pic_offset_table
|
||
* FUNCTION_FLAGS_USES_PIC_OFFSET_TABLE)
|
||
+ current_function_has_computed_jump * FUNCTION_FLAGS_HAS_COMPUTED_JUMP);
|
||
|
||
/* Clear out PARMDECL_MAP. It was allocated in the caller's frame. */
|
||
bzero ((char *) parmdecl_map, max_parm_reg * sizeof (tree));
|
||
arg_vector = rtvec_alloc (list_length (DECL_ARGUMENTS (fndecl)));
|
||
|
||
for (parms = DECL_ARGUMENTS (fndecl), i = 0;
|
||
parms;
|
||
parms = TREE_CHAIN (parms), i++)
|
||
{
|
||
rtx p = DECL_RTL (parms);
|
||
int copied_incoming = 0;
|
||
|
||
/* If we have (mem (addressof (mem ...))), use the inner MEM since
|
||
otherwise the copy_rtx call below will not unshare the MEM since
|
||
it shares ADDRESSOF. */
|
||
if (GET_CODE (p) == MEM && GET_CODE (XEXP (p, 0)) == ADDRESSOF
|
||
&& GET_CODE (XEXP (XEXP (p, 0), 0)) == MEM)
|
||
p = XEXP (XEXP (p, 0), 0);
|
||
|
||
if (GET_CODE (p) == MEM && copy)
|
||
{
|
||
/* Copy the rtl so that modifications of the addresses
|
||
later in compilation won't affect this arg_vector.
|
||
Virtual register instantiation can screw the address
|
||
of the rtl. */
|
||
rtx new = copy_rtx (p);
|
||
|
||
/* Don't leave the old copy anywhere in this decl. */
|
||
if (DECL_RTL (parms) == DECL_INCOMING_RTL (parms)
|
||
|| (GET_CODE (DECL_RTL (parms)) == MEM
|
||
&& GET_CODE (DECL_INCOMING_RTL (parms)) == MEM
|
||
&& (XEXP (DECL_RTL (parms), 0)
|
||
== XEXP (DECL_INCOMING_RTL (parms), 0))))
|
||
DECL_INCOMING_RTL (parms) = new, copied_incoming = 1;
|
||
|
||
DECL_RTL (parms) = new;
|
||
}
|
||
|
||
RTVEC_ELT (arg_vector, i) = p;
|
||
|
||
if (GET_CODE (p) == REG)
|
||
parmdecl_map[REGNO (p)] = parms;
|
||
else if (GET_CODE (p) == CONCAT)
|
||
{
|
||
rtx preal = gen_realpart (GET_MODE (XEXP (p, 0)), p);
|
||
rtx pimag = gen_imagpart (GET_MODE (preal), p);
|
||
|
||
if (GET_CODE (preal) == REG)
|
||
parmdecl_map[REGNO (preal)] = parms;
|
||
if (GET_CODE (pimag) == REG)
|
||
parmdecl_map[REGNO (pimag)] = parms;
|
||
}
|
||
|
||
/* This flag is cleared later
|
||
if the function ever modifies the value of the parm. */
|
||
TREE_READONLY (parms) = 1;
|
||
|
||
/* Copy DECL_INCOMING_RTL if not done already. This can
|
||
happen if DECL_RTL is a reg. */
|
||
if (copy && ! copied_incoming)
|
||
{
|
||
p = DECL_INCOMING_RTL (parms);
|
||
|
||
/* If we have (mem (addressof (mem ...))), use the inner MEM since
|
||
otherwise the copy_rtx call below will not unshare the MEM since
|
||
it shares ADDRESSOF. */
|
||
if (GET_CODE (p) == MEM && GET_CODE (XEXP (p, 0)) == ADDRESSOF
|
||
&& GET_CODE (XEXP (XEXP (p, 0), 0)) == MEM)
|
||
p = XEXP (XEXP (p, 0), 0);
|
||
|
||
if (GET_CODE (p) == MEM)
|
||
DECL_INCOMING_RTL (parms) = copy_rtx (p);
|
||
}
|
||
}
|
||
|
||
/* Assume we start out in the insns that set up the parameters. */
|
||
in_nonparm_insns = 0;
|
||
|
||
/* The list of DECL_SAVED_INSNS, starts off with a header which
|
||
contains the following information:
|
||
|
||
the first insn of the function (not including the insns that copy
|
||
parameters into registers).
|
||
the first parameter insn of the function,
|
||
the first label used by that function,
|
||
the last label used by that function,
|
||
the highest register number used for parameters,
|
||
the total number of registers used,
|
||
the size of the incoming stack area for parameters,
|
||
the number of bytes popped on return,
|
||
the stack slot list,
|
||
the labels that are forced to exist,
|
||
some flags that are used to restore compiler globals,
|
||
the value of current_function_outgoing_args_size,
|
||
the original argument vector,
|
||
the original DECL_INITIAL,
|
||
and pointers to the table of pseudo regs, pointer flags, and alignment. */
|
||
|
||
return gen_inline_header_rtx (NULL_RTX, NULL_RTX, min_labelno, max_labelno,
|
||
max_parm_reg, max_reg,
|
||
current_function_args_size,
|
||
current_function_pops_args,
|
||
stack_slot_list, forced_labels, function_flags,
|
||
current_function_outgoing_args_size,
|
||
arg_vector, (rtx) DECL_INITIAL (fndecl),
|
||
(rtvec) regno_reg_rtx, regno_pointer_flag,
|
||
regno_pointer_align,
|
||
(rtvec) parm_reg_stack_loc);
|
||
}
|
||
|
||
/* Subroutine for `save_for_inline{copying,nocopy}'. Finishes up the
|
||
things that must be done to make FNDECL expandable as an inline function.
|
||
HEAD contains the chain of insns to which FNDECL will expand. */
|
||
|
||
static void
|
||
finish_inline (fndecl, head)
|
||
tree fndecl;
|
||
rtx head;
|
||
{
|
||
FIRST_FUNCTION_INSN (head) = get_first_nonparm_insn ();
|
||
FIRST_PARM_INSN (head) = get_insns ();
|
||
DECL_SAVED_INSNS (fndecl) = head;
|
||
DECL_FRAME_SIZE (fndecl) = get_frame_size ();
|
||
}
|
||
|
||
/* Adjust the BLOCK_END_NOTE pointers in a given copied DECL tree so that
|
||
they all point to the new (copied) rtxs. */
|
||
|
||
static void
|
||
adjust_copied_decl_tree (block)
|
||
register tree block;
|
||
{
|
||
register tree subblock;
|
||
register rtx original_end;
|
||
|
||
original_end = BLOCK_END_NOTE (block);
|
||
if (original_end)
|
||
{
|
||
BLOCK_END_NOTE (block) = (rtx) NOTE_SOURCE_FILE (original_end);
|
||
NOTE_SOURCE_FILE (original_end) = 0;
|
||
}
|
||
|
||
/* Process all subblocks. */
|
||
for (subblock = BLOCK_SUBBLOCKS (block);
|
||
subblock;
|
||
subblock = TREE_CHAIN (subblock))
|
||
adjust_copied_decl_tree (subblock);
|
||
}
|
||
|
||
/* Make the insns and PARM_DECLs of the current function permanent
|
||
and record other information in DECL_SAVED_INSNS to allow inlining
|
||
of this function in subsequent calls.
|
||
|
||
This function is called when we are going to immediately compile
|
||
the insns for FNDECL. The insns in maybepermanent_obstack cannot be
|
||
modified by the compilation process, so we copy all of them to
|
||
new storage and consider the new insns to be the insn chain to be
|
||
compiled. Our caller (rest_of_compilation) saves the original
|
||
DECL_INITIAL and DECL_ARGUMENTS; here we copy them. */
|
||
|
||
/* ??? The nonlocal_label list should be adjusted also. However, since
|
||
a function that contains a nested function never gets inlined currently,
|
||
the nonlocal_label list will always be empty, so we don't worry about
|
||
it for now. */
|
||
|
||
void
|
||
save_for_inline_copying (fndecl)
|
||
tree fndecl;
|
||
{
|
||
rtx first_insn, last_insn, insn;
|
||
rtx head, copy;
|
||
int max_labelno, min_labelno, i, len;
|
||
int max_reg;
|
||
int max_uid;
|
||
rtx first_nonparm_insn;
|
||
char *new, *new1;
|
||
rtx *new_parm_reg_stack_loc;
|
||
rtx *new2;
|
||
|
||
/* Make and emit a return-label if we have not already done so.
|
||
Do this before recording the bounds on label numbers. */
|
||
|
||
if (return_label == 0)
|
||
{
|
||
return_label = gen_label_rtx ();
|
||
emit_label (return_label);
|
||
}
|
||
|
||
/* Get some bounds on the labels and registers used. */
|
||
|
||
max_labelno = max_label_num ();
|
||
min_labelno = get_first_label_num ();
|
||
max_reg = max_reg_num ();
|
||
|
||
/* Set up PARMDECL_MAP which maps pseudo-reg number to its PARM_DECL.
|
||
Later we set TREE_READONLY to 0 if the parm is modified inside the fn.
|
||
Also set up ARG_VECTOR, which holds the unmodified DECL_RTX values
|
||
for the parms, prior to elimination of virtual registers.
|
||
These values are needed for substituting parms properly. */
|
||
|
||
parmdecl_map = (tree *) alloca (max_parm_reg * sizeof (tree));
|
||
|
||
head = initialize_for_inline (fndecl, min_labelno, max_labelno, max_reg, 1);
|
||
|
||
if (current_function_uses_const_pool)
|
||
{
|
||
/* Replace any constant pool references with the actual constant. We
|
||
will put the constants back in the copy made below. */
|
||
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) == 'i')
|
||
{
|
||
save_constants (&PATTERN (insn));
|
||
if (REG_NOTES (insn))
|
||
save_constants (®_NOTES (insn));
|
||
}
|
||
|
||
/* Also scan all decls, and replace any constant pool references with the
|
||
actual constant. */
|
||
save_constants_in_decl_trees (DECL_INITIAL (fndecl));
|
||
|
||
/* Clear out the constant pool so that we can recreate it with the
|
||
copied constants below. */
|
||
init_const_rtx_hash_table ();
|
||
clear_const_double_mem ();
|
||
}
|
||
|
||
max_uid = INSN_UID (head);
|
||
|
||
/* We have now allocated all that needs to be allocated permanently
|
||
on the rtx obstack. Set our high-water mark, so that we
|
||
can free the rest of this when the time comes. */
|
||
|
||
preserve_data ();
|
||
|
||
/* Copy the chain insns of this function.
|
||
Install the copied chain as the insns of this function,
|
||
for continued compilation;
|
||
the original chain is recorded as the DECL_SAVED_INSNS
|
||
for inlining future calls. */
|
||
|
||
/* If there are insns that copy parms from the stack into pseudo registers,
|
||
those insns are not copied. `expand_inline_function' must
|
||
emit the correct code to handle such things. */
|
||
|
||
insn = get_insns ();
|
||
if (GET_CODE (insn) != NOTE)
|
||
abort ();
|
||
first_insn = rtx_alloc (NOTE);
|
||
NOTE_SOURCE_FILE (first_insn) = NOTE_SOURCE_FILE (insn);
|
||
NOTE_LINE_NUMBER (first_insn) = NOTE_LINE_NUMBER (insn);
|
||
INSN_UID (first_insn) = INSN_UID (insn);
|
||
PREV_INSN (first_insn) = NULL;
|
||
NEXT_INSN (first_insn) = NULL;
|
||
last_insn = first_insn;
|
||
|
||
/* Each pseudo-reg in the old insn chain must have a unique rtx in the copy.
|
||
Make these new rtx's now, and install them in regno_reg_rtx, so they
|
||
will be the official pseudo-reg rtx's for the rest of compilation. */
|
||
|
||
reg_map = (rtx *) savealloc (regno_pointer_flag_length * sizeof (rtx));
|
||
|
||
len = sizeof (struct rtx_def) + (GET_RTX_LENGTH (REG) - 1) * sizeof (rtunion);
|
||
for (i = max_reg - 1; i > LAST_VIRTUAL_REGISTER; i--)
|
||
reg_map[i] = (rtx)obstack_copy (function_maybepermanent_obstack,
|
||
regno_reg_rtx[i], len);
|
||
|
||
regno_reg_rtx = reg_map;
|
||
|
||
/* Put copies of all the virtual register rtx into the new regno_reg_rtx. */
|
||
init_virtual_regs ();
|
||
|
||
/* Likewise each label rtx must have a unique rtx as its copy. */
|
||
|
||
/* We used to use alloca here, but the size of what it would try to
|
||
allocate would occasionally cause it to exceed the stack limit and
|
||
cause unpredictable core dumps. Some examples were > 2Mb in size. */
|
||
label_map = (rtx *) xmalloc ((max_labelno) * sizeof (rtx));
|
||
|
||
for (i = min_labelno; i < max_labelno; i++)
|
||
label_map[i] = gen_label_rtx ();
|
||
|
||
/* Likewise for parm_reg_stack_slot. */
|
||
new_parm_reg_stack_loc = (rtx *) savealloc (max_parm_reg * sizeof (rtx));
|
||
for (i = 0; i < max_parm_reg; i++)
|
||
new_parm_reg_stack_loc[i] = copy_for_inline (parm_reg_stack_loc[i]);
|
||
|
||
parm_reg_stack_loc = new_parm_reg_stack_loc;
|
||
|
||
/* Record the mapping of old insns to copied insns. */
|
||
|
||
insn_map = (rtx *) alloca (max_uid * sizeof (rtx));
|
||
bzero ((char *) insn_map, max_uid * sizeof (rtx));
|
||
|
||
/* Get the insn which signals the end of parameter setup code. */
|
||
first_nonparm_insn = get_first_nonparm_insn ();
|
||
|
||
/* Copy any entries in regno_reg_rtx or DECL_RTLs that reference MEM
|
||
(the former occurs when a variable has its address taken)
|
||
since these may be shared and can be changed by virtual
|
||
register instantiation. DECL_RTL values for our arguments
|
||
have already been copied by initialize_for_inline. */
|
||
for (i = LAST_VIRTUAL_REGISTER + 1; i < max_reg; i++)
|
||
if (GET_CODE (regno_reg_rtx[i]) == MEM)
|
||
XEXP (regno_reg_rtx[i], 0)
|
||
= copy_for_inline (XEXP (regno_reg_rtx[i], 0));
|
||
|
||
/* Copy the parm_reg_stack_loc array, and substitute for all of the rtx
|
||
contained in it. */
|
||
new2 = (rtx *) savealloc (max_parm_reg * sizeof (rtx));
|
||
bcopy ((char *) parm_reg_stack_loc, (char *) new2,
|
||
max_parm_reg * sizeof (rtx));
|
||
parm_reg_stack_loc = new2;
|
||
for (i = LAST_VIRTUAL_REGISTER + 1; i < max_parm_reg; ++i)
|
||
if (parm_reg_stack_loc[i])
|
||
parm_reg_stack_loc[i] = copy_for_inline (parm_reg_stack_loc[i]);
|
||
|
||
/* Copy the tree of subblocks of the function, and the decls in them.
|
||
We will use the copy for compiling this function, then restore the original
|
||
subblocks and decls for use when inlining this function.
|
||
|
||
Several parts of the compiler modify BLOCK trees. In particular,
|
||
instantiate_virtual_regs will instantiate any virtual regs
|
||
mentioned in the DECL_RTLs of the decls, and loop
|
||
unrolling will replicate any BLOCK trees inside an unrolled loop.
|
||
|
||
The modified subblocks or DECL_RTLs would be incorrect for the original rtl
|
||
which we will use for inlining. The rtl might even contain pseudoregs
|
||
whose space has been freed. */
|
||
|
||
DECL_INITIAL (fndecl) = copy_decl_tree (DECL_INITIAL (fndecl));
|
||
DECL_ARGUMENTS (fndecl) = copy_decl_list (DECL_ARGUMENTS (fndecl));
|
||
|
||
/* Now copy each DECL_RTL which is a MEM,
|
||
so it is safe to modify their addresses. */
|
||
copy_decl_rtls (DECL_INITIAL (fndecl));
|
||
|
||
/* The fndecl node acts as its own progenitor, so mark it as such. */
|
||
DECL_ABSTRACT_ORIGIN (fndecl) = fndecl;
|
||
|
||
/* Now copy the chain of insns. Do this twice. The first copy the insn
|
||
itself and its body. The second time copy of REG_NOTES. This is because
|
||
a REG_NOTE may have a forward pointer to another insn. */
|
||
|
||
for (insn = NEXT_INSN (insn); insn; insn = NEXT_INSN (insn))
|
||
{
|
||
orig_asm_operands_vector = 0;
|
||
|
||
if (insn == first_nonparm_insn)
|
||
in_nonparm_insns = 1;
|
||
|
||
switch (GET_CODE (insn))
|
||
{
|
||
case NOTE:
|
||
/* No need to keep these. */
|
||
if (NOTE_LINE_NUMBER (insn) == NOTE_INSN_DELETED)
|
||
continue;
|
||
|
||
copy = rtx_alloc (NOTE);
|
||
NOTE_LINE_NUMBER (copy) = NOTE_LINE_NUMBER (insn);
|
||
if (NOTE_LINE_NUMBER (insn) != NOTE_INSN_BLOCK_END)
|
||
NOTE_SOURCE_FILE (copy) = NOTE_SOURCE_FILE (insn);
|
||
else
|
||
{
|
||
NOTE_SOURCE_FILE (insn) = (char *) copy;
|
||
NOTE_SOURCE_FILE (copy) = 0;
|
||
}
|
||
if (NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_BEG
|
||
|| NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_END)
|
||
{
|
||
int new_region = CODE_LABEL_NUMBER
|
||
(label_map[NOTE_BLOCK_NUMBER (copy)]);
|
||
|
||
/* we have to duplicate the handlers for the original */
|
||
if (NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_BEG)
|
||
duplicate_eh_handlers (NOTE_BLOCK_NUMBER (copy), new_region,
|
||
save_for_inline_eh_labelmap);
|
||
|
||
/* We have to forward these both to match the new exception
|
||
region. */
|
||
NOTE_BLOCK_NUMBER (copy) = new_region;
|
||
|
||
}
|
||
RTX_INTEGRATED_P (copy) = RTX_INTEGRATED_P (insn);
|
||
break;
|
||
|
||
case INSN:
|
||
case JUMP_INSN:
|
||
case CALL_INSN:
|
||
copy = rtx_alloc (GET_CODE (insn));
|
||
|
||
if (GET_CODE (insn) == CALL_INSN)
|
||
CALL_INSN_FUNCTION_USAGE (copy)
|
||
= copy_for_inline (CALL_INSN_FUNCTION_USAGE (insn));
|
||
|
||
PATTERN (copy) = copy_for_inline (PATTERN (insn));
|
||
INSN_CODE (copy) = -1;
|
||
LOG_LINKS (copy) = NULL_RTX;
|
||
RTX_INTEGRATED_P (copy) = RTX_INTEGRATED_P (insn);
|
||
break;
|
||
|
||
case CODE_LABEL:
|
||
copy = label_map[CODE_LABEL_NUMBER (insn)];
|
||
LABEL_NAME (copy) = LABEL_NAME (insn);
|
||
break;
|
||
|
||
case BARRIER:
|
||
copy = rtx_alloc (BARRIER);
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
INSN_UID (copy) = INSN_UID (insn);
|
||
insn_map[INSN_UID (insn)] = copy;
|
||
NEXT_INSN (last_insn) = copy;
|
||
PREV_INSN (copy) = last_insn;
|
||
last_insn = copy;
|
||
}
|
||
|
||
adjust_copied_decl_tree (DECL_INITIAL (fndecl));
|
||
|
||
/* Now copy the REG_NOTES. */
|
||
for (insn = NEXT_INSN (get_insns ()); insn; insn = NEXT_INSN (insn))
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) == 'i'
|
||
&& insn_map[INSN_UID(insn)])
|
||
REG_NOTES (insn_map[INSN_UID (insn)])
|
||
= copy_for_inline (REG_NOTES (insn));
|
||
|
||
NEXT_INSN (last_insn) = NULL;
|
||
|
||
finish_inline (fndecl, head);
|
||
|
||
/* Make new versions of the register tables. */
|
||
new = (char *) savealloc (regno_pointer_flag_length);
|
||
bcopy (regno_pointer_flag, new, regno_pointer_flag_length);
|
||
new1 = (char *) savealloc (regno_pointer_flag_length);
|
||
bcopy (regno_pointer_align, new1, regno_pointer_flag_length);
|
||
|
||
regno_pointer_flag = new;
|
||
regno_pointer_align = new1;
|
||
|
||
set_new_first_and_last_insn (first_insn, last_insn);
|
||
|
||
if (label_map)
|
||
free (label_map);
|
||
}
|
||
|
||
/* Copy NODE (as with copy_node). NODE must be a DECL. Set the
|
||
DECL_ABSTRACT_ORIGIN for the new accordinly. */
|
||
|
||
static tree
|
||
copy_and_set_decl_abstract_origin (node)
|
||
tree node;
|
||
{
|
||
tree copy = copy_node (node);
|
||
if (DECL_ABSTRACT_ORIGIN (copy) != NULL_TREE)
|
||
/* That means that NODE already had a DECL_ABSTRACT_ORIGIN. (This
|
||
situation occurs if we inline a function which itself made
|
||
calls to inline functions.) Since DECL_ABSTRACT_ORIGIN is the
|
||
most distant ancestor, we don't have to do anything here. */
|
||
;
|
||
else
|
||
/* The most distant ancestor must be NODE. */
|
||
DECL_ABSTRACT_ORIGIN (copy) = node;
|
||
|
||
return copy;
|
||
}
|
||
|
||
/* Return a copy of a chain of nodes, chained through the TREE_CHAIN field.
|
||
For example, this can copy a list made of TREE_LIST nodes. While copying,
|
||
set DECL_ABSTRACT_ORIGIN appropriately. */
|
||
|
||
static tree
|
||
copy_decl_list (list)
|
||
tree list;
|
||
{
|
||
tree head;
|
||
register tree prev, next;
|
||
|
||
if (list == 0)
|
||
return 0;
|
||
|
||
head = prev = copy_and_set_decl_abstract_origin (list);
|
||
next = TREE_CHAIN (list);
|
||
while (next)
|
||
{
|
||
register tree copy;
|
||
|
||
copy = copy_and_set_decl_abstract_origin (next);
|
||
TREE_CHAIN (prev) = copy;
|
||
prev = copy;
|
||
next = TREE_CHAIN (next);
|
||
}
|
||
return head;
|
||
}
|
||
|
||
/* Make a copy of the entire tree of blocks BLOCK, and return it. */
|
||
|
||
static tree
|
||
copy_decl_tree (block)
|
||
tree block;
|
||
{
|
||
tree t, vars, subblocks;
|
||
|
||
vars = copy_decl_list (BLOCK_VARS (block));
|
||
subblocks = 0;
|
||
|
||
/* Process all subblocks. */
|
||
for (t = BLOCK_SUBBLOCKS (block); t; t = TREE_CHAIN (t))
|
||
{
|
||
tree copy = copy_decl_tree (t);
|
||
TREE_CHAIN (copy) = subblocks;
|
||
subblocks = copy;
|
||
}
|
||
|
||
t = copy_node (block);
|
||
BLOCK_VARS (t) = vars;
|
||
BLOCK_SUBBLOCKS (t) = nreverse (subblocks);
|
||
/* If the BLOCK being cloned is already marked as having been instantiated
|
||
from something else, then leave that `origin' marking alone. Otherwise,
|
||
mark the clone as having originated from the BLOCK we are cloning. */
|
||
if (BLOCK_ABSTRACT_ORIGIN (t) == NULL_TREE)
|
||
BLOCK_ABSTRACT_ORIGIN (t) = block;
|
||
return t;
|
||
}
|
||
|
||
/* Copy DECL_RTLs in all decls in the given BLOCK node. */
|
||
|
||
static void
|
||
copy_decl_rtls (block)
|
||
tree block;
|
||
{
|
||
tree t;
|
||
|
||
for (t = BLOCK_VARS (block); t; t = TREE_CHAIN (t))
|
||
if (DECL_RTL (t) && GET_CODE (DECL_RTL (t)) == MEM)
|
||
DECL_RTL (t) = copy_for_inline (DECL_RTL (t));
|
||
|
||
/* Process all subblocks. */
|
||
for (t = BLOCK_SUBBLOCKS (block); t; t = TREE_CHAIN (t))
|
||
copy_decl_rtls (t);
|
||
}
|
||
|
||
/* Make the insns and PARM_DECLs of the current function permanent
|
||
and record other information in DECL_SAVED_INSNS to allow inlining
|
||
of this function in subsequent calls.
|
||
|
||
This routine need not copy any insns because we are not going
|
||
to immediately compile the insns in the insn chain. There
|
||
are two cases when we would compile the insns for FNDECL:
|
||
(1) when FNDECL is expanded inline, and (2) when FNDECL needs to
|
||
be output at the end of other compilation, because somebody took
|
||
its address. In the first case, the insns of FNDECL are copied
|
||
as it is expanded inline, so FNDECL's saved insns are not
|
||
modified. In the second case, FNDECL is used for the last time,
|
||
so modifying the rtl is not a problem.
|
||
|
||
We don't have to worry about FNDECL being inline expanded by
|
||
other functions which are written at the end of compilation
|
||
because flag_no_inline is turned on when we begin writing
|
||
functions at the end of compilation. */
|
||
|
||
void
|
||
save_for_inline_nocopy (fndecl)
|
||
tree fndecl;
|
||
{
|
||
rtx insn;
|
||
rtx head;
|
||
rtx first_nonparm_insn;
|
||
|
||
/* Set up PARMDECL_MAP which maps pseudo-reg number to its PARM_DECL.
|
||
Later we set TREE_READONLY to 0 if the parm is modified inside the fn.
|
||
Also set up ARG_VECTOR, which holds the unmodified DECL_RTX values
|
||
for the parms, prior to elimination of virtual registers.
|
||
These values are needed for substituting parms properly. */
|
||
|
||
parmdecl_map = (tree *) alloca (max_parm_reg * sizeof (tree));
|
||
|
||
/* Make and emit a return-label if we have not already done so. */
|
||
|
||
if (return_label == 0)
|
||
{
|
||
return_label = gen_label_rtx ();
|
||
emit_label (return_label);
|
||
}
|
||
|
||
head = initialize_for_inline (fndecl, get_first_label_num (),
|
||
max_label_num (), max_reg_num (), 0);
|
||
|
||
/* If there are insns that copy parms from the stack into pseudo registers,
|
||
those insns are not copied. `expand_inline_function' must
|
||
emit the correct code to handle such things. */
|
||
|
||
insn = get_insns ();
|
||
if (GET_CODE (insn) != NOTE)
|
||
abort ();
|
||
|
||
/* Get the insn which signals the end of parameter setup code. */
|
||
first_nonparm_insn = get_first_nonparm_insn ();
|
||
|
||
/* Now just scan the chain of insns to see what happens to our
|
||
PARM_DECLs. If a PARM_DECL is used but never modified, we
|
||
can substitute its rtl directly when expanding inline (and
|
||
perform constant folding when its incoming value is constant).
|
||
Otherwise, we have to copy its value into a new register and track
|
||
the new register's life. */
|
||
|
||
for (insn = NEXT_INSN (insn); insn; insn = NEXT_INSN (insn))
|
||
{
|
||
if (insn == first_nonparm_insn)
|
||
in_nonparm_insns = 1;
|
||
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) == 'i')
|
||
{
|
||
if (current_function_uses_const_pool)
|
||
{
|
||
/* Replace any constant pool references with the actual constant.
|
||
We will put the constant back if we need to write the
|
||
function out after all. */
|
||
save_constants (&PATTERN (insn));
|
||
if (REG_NOTES (insn))
|
||
save_constants (®_NOTES (insn));
|
||
}
|
||
|
||
/* Record what interesting things happen to our parameters. */
|
||
note_stores (PATTERN (insn), note_modified_parmregs);
|
||
}
|
||
}
|
||
|
||
/* Also scan all decls, and replace any constant pool references with the
|
||
actual constant. */
|
||
save_constants_in_decl_trees (DECL_INITIAL (fndecl));
|
||
|
||
/* We have now allocated all that needs to be allocated permanently
|
||
on the rtx obstack. Set our high-water mark, so that we
|
||
can free the rest of this when the time comes. */
|
||
|
||
preserve_data ();
|
||
|
||
finish_inline (fndecl, head);
|
||
}
|
||
|
||
/* Given PX, a pointer into an insn, search for references to the constant
|
||
pool. Replace each with a CONST that has the mode of the original
|
||
constant, contains the constant, and has RTX_INTEGRATED_P set.
|
||
Similarly, constant pool addresses not enclosed in a MEM are replaced
|
||
with an ADDRESS and CONST rtx which also gives the constant, its
|
||
mode, the mode of the address, and has RTX_INTEGRATED_P set. */
|
||
|
||
static void
|
||
save_constants (px)
|
||
rtx *px;
|
||
{
|
||
rtx x;
|
||
int i, j;
|
||
|
||
again:
|
||
x = *px;
|
||
|
||
/* If this is a CONST_DOUBLE, don't try to fix things up in
|
||
CONST_DOUBLE_MEM, because this is an infinite recursion. */
|
||
if (GET_CODE (x) == CONST_DOUBLE)
|
||
return;
|
||
else if (GET_CODE (x) == MEM && GET_CODE (XEXP (x, 0)) == SYMBOL_REF
|
||
&& CONSTANT_POOL_ADDRESS_P (XEXP (x,0)))
|
||
{
|
||
enum machine_mode const_mode = get_pool_mode (XEXP (x, 0));
|
||
rtx new = gen_rtx_CONST (const_mode, get_pool_constant (XEXP (x, 0)));
|
||
RTX_INTEGRATED_P (new) = 1;
|
||
|
||
/* If the MEM was in a different mode than the constant (perhaps we
|
||
were only looking at the low-order part), surround it with a
|
||
SUBREG so we can save both modes. */
|
||
|
||
if (GET_MODE (x) != const_mode)
|
||
{
|
||
new = gen_rtx_SUBREG (GET_MODE (x), new, 0);
|
||
RTX_INTEGRATED_P (new) = 1;
|
||
}
|
||
|
||
*px = new;
|
||
save_constants (&XEXP (*px, 0));
|
||
}
|
||
else if (GET_CODE (x) == SYMBOL_REF
|
||
&& CONSTANT_POOL_ADDRESS_P (x))
|
||
{
|
||
*px = gen_rtx_ADDRESS (GET_MODE (x),
|
||
gen_rtx_CONST (get_pool_mode (x),
|
||
get_pool_constant (x)));
|
||
save_constants (&XEXP (*px, 0));
|
||
RTX_INTEGRATED_P (*px) = 1;
|
||
}
|
||
|
||
else
|
||
{
|
||
char *fmt = GET_RTX_FORMAT (GET_CODE (x));
|
||
int len = GET_RTX_LENGTH (GET_CODE (x));
|
||
|
||
for (i = len-1; i >= 0; i--)
|
||
{
|
||
switch (fmt[i])
|
||
{
|
||
case 'E':
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
save_constants (&XVECEXP (x, i, j));
|
||
break;
|
||
|
||
case 'e':
|
||
if (XEXP (x, i) == 0)
|
||
continue;
|
||
if (i == 0)
|
||
{
|
||
/* Hack tail-recursion here. */
|
||
px = &XEXP (x, 0);
|
||
goto again;
|
||
}
|
||
save_constants (&XEXP (x, i));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Note whether a parameter is modified or not. */
|
||
|
||
static void
|
||
note_modified_parmregs (reg, x)
|
||
rtx reg;
|
||
rtx x ATTRIBUTE_UNUSED;
|
||
{
|
||
if (GET_CODE (reg) == REG && in_nonparm_insns
|
||
&& REGNO (reg) < max_parm_reg
|
||
&& REGNO (reg) >= FIRST_PSEUDO_REGISTER
|
||
&& parmdecl_map[REGNO (reg)] != 0)
|
||
TREE_READONLY (parmdecl_map[REGNO (reg)]) = 0;
|
||
}
|
||
|
||
/* Copy the rtx ORIG recursively, replacing pseudo-regs and labels
|
||
according to `reg_map' and `label_map'. The original rtl insns
|
||
will be saved for inlining; this is used to make a copy
|
||
which is used to finish compiling the inline function itself.
|
||
|
||
If we find a "saved" constant pool entry, one which was replaced with
|
||
the value of the constant, convert it back to a constant pool entry.
|
||
Since the pool wasn't touched, this should simply restore the old
|
||
address.
|
||
|
||
All other kinds of rtx are copied except those that can never be
|
||
changed during compilation. */
|
||
|
||
static rtx
|
||
copy_for_inline (orig)
|
||
rtx orig;
|
||
{
|
||
register rtx x = orig;
|
||
register rtx new;
|
||
register int i;
|
||
register enum rtx_code code;
|
||
register char *format_ptr;
|
||
|
||
if (x == 0)
|
||
return x;
|
||
|
||
code = GET_CODE (x);
|
||
|
||
/* These types may be freely shared. */
|
||
|
||
switch (code)
|
||
{
|
||
case QUEUED:
|
||
case CONST_INT:
|
||
case PC:
|
||
case CC0:
|
||
return x;
|
||
|
||
case SYMBOL_REF:
|
||
if (! SYMBOL_REF_NEED_ADJUST (x))
|
||
return x;
|
||
return rethrow_symbol_map (x, save_for_inline_eh_labelmap);
|
||
|
||
case CONST_DOUBLE:
|
||
/* We have to make a new CONST_DOUBLE to ensure that we account for
|
||
it correctly. Using the old CONST_DOUBLE_MEM data is wrong. */
|
||
if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT)
|
||
{
|
||
REAL_VALUE_TYPE d;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (d, x);
|
||
return CONST_DOUBLE_FROM_REAL_VALUE (d, GET_MODE (x));
|
||
}
|
||
else
|
||
return immed_double_const (CONST_DOUBLE_LOW (x), CONST_DOUBLE_HIGH (x),
|
||
VOIDmode);
|
||
|
||
case CONST:
|
||
/* Get constant pool entry for constant in the pool. */
|
||
if (RTX_INTEGRATED_P (x))
|
||
return validize_mem (force_const_mem (GET_MODE (x),
|
||
copy_for_inline (XEXP (x, 0))));
|
||
break;
|
||
|
||
case SUBREG:
|
||
/* Get constant pool entry, but access in different mode. */
|
||
if (RTX_INTEGRATED_P (x))
|
||
{
|
||
new = force_const_mem (GET_MODE (SUBREG_REG (x)),
|
||
copy_for_inline (XEXP (SUBREG_REG (x), 0)));
|
||
|
||
PUT_MODE (new, GET_MODE (x));
|
||
return validize_mem (new);
|
||
}
|
||
break;
|
||
|
||
case ADDRESS:
|
||
/* If not special for constant pool error. Else get constant pool
|
||
address. */
|
||
if (! RTX_INTEGRATED_P (x))
|
||
abort ();
|
||
|
||
new = force_const_mem (GET_MODE (XEXP (x, 0)),
|
||
copy_for_inline (XEXP (XEXP (x, 0), 0)));
|
||
new = XEXP (new, 0);
|
||
|
||
#ifdef POINTERS_EXTEND_UNSIGNED
|
||
if (GET_MODE (new) != GET_MODE (x))
|
||
new = convert_memory_address (GET_MODE (x), new);
|
||
#endif
|
||
|
||
return new;
|
||
|
||
case ASM_OPERANDS:
|
||
/* If a single asm insn contains multiple output operands
|
||
then it contains multiple ASM_OPERANDS rtx's that share operand 3.
|
||
We must make sure that the copied insn continues to share it. */
|
||
if (orig_asm_operands_vector == XVEC (orig, 3))
|
||
{
|
||
x = rtx_alloc (ASM_OPERANDS);
|
||
x->volatil = orig->volatil;
|
||
XSTR (x, 0) = XSTR (orig, 0);
|
||
XSTR (x, 1) = XSTR (orig, 1);
|
||
XINT (x, 2) = XINT (orig, 2);
|
||
XVEC (x, 3) = copy_asm_operands_vector;
|
||
XVEC (x, 4) = copy_asm_constraints_vector;
|
||
XSTR (x, 5) = XSTR (orig, 5);
|
||
XINT (x, 6) = XINT (orig, 6);
|
||
return x;
|
||
}
|
||
break;
|
||
|
||
case MEM:
|
||
/* A MEM is usually allowed to be shared if its address is constant
|
||
or is a constant plus one of the special registers.
|
||
|
||
We do not allow sharing of addresses that are either a special
|
||
register or the sum of a constant and a special register because
|
||
it is possible for unshare_all_rtl to copy the address, into memory
|
||
that won't be saved. Although the MEM can safely be shared, and
|
||
won't be copied there, the address itself cannot be shared, and may
|
||
need to be copied.
|
||
|
||
There are also two exceptions with constants: The first is if the
|
||
constant is a LABEL_REF or the sum of the LABEL_REF
|
||
and an integer. This case can happen if we have an inline
|
||
function that supplies a constant operand to the call of another
|
||
inline function that uses it in a switch statement. In this case,
|
||
we will be replacing the LABEL_REF, so we have to replace this MEM
|
||
as well.
|
||
|
||
The second case is if we have a (const (plus (address ..) ...)).
|
||
In that case we need to put back the address of the constant pool
|
||
entry. */
|
||
|
||
if (CONSTANT_ADDRESS_P (XEXP (x, 0))
|
||
&& GET_CODE (XEXP (x, 0)) != LABEL_REF
|
||
&& ! (GET_CODE (XEXP (x, 0)) == CONST
|
||
&& (GET_CODE (XEXP (XEXP (x, 0), 0)) == PLUS
|
||
&& ((GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 0))
|
||
== LABEL_REF)
|
||
|| (GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 0))
|
||
== ADDRESS)))))
|
||
return x;
|
||
break;
|
||
|
||
case LABEL_REF:
|
||
/* If this is a non-local label, just make a new LABEL_REF.
|
||
Otherwise, use the new label as well. */
|
||
x = gen_rtx_LABEL_REF (GET_MODE (orig),
|
||
LABEL_REF_NONLOCAL_P (orig) ? XEXP (orig, 0)
|
||
: label_map[CODE_LABEL_NUMBER (XEXP (orig, 0))]);
|
||
LABEL_REF_NONLOCAL_P (x) = LABEL_REF_NONLOCAL_P (orig);
|
||
LABEL_OUTSIDE_LOOP_P (x) = LABEL_OUTSIDE_LOOP_P (orig);
|
||
return x;
|
||
|
||
case REG:
|
||
if (REGNO (x) > LAST_VIRTUAL_REGISTER)
|
||
return reg_map [REGNO (x)];
|
||
else
|
||
return x;
|
||
|
||
case SET:
|
||
/* If a parm that gets modified lives in a pseudo-reg,
|
||
clear its TREE_READONLY to prevent certain optimizations. */
|
||
{
|
||
rtx dest = SET_DEST (x);
|
||
|
||
while (GET_CODE (dest) == STRICT_LOW_PART
|
||
|| GET_CODE (dest) == ZERO_EXTRACT
|
||
|| GET_CODE (dest) == SUBREG)
|
||
dest = XEXP (dest, 0);
|
||
|
||
if (GET_CODE (dest) == REG
|
||
&& REGNO (dest) < max_parm_reg
|
||
&& REGNO (dest) >= FIRST_PSEUDO_REGISTER
|
||
&& parmdecl_map[REGNO (dest)] != 0
|
||
/* The insn to load an arg pseudo from a stack slot
|
||
does not count as modifying it. */
|
||
&& in_nonparm_insns)
|
||
TREE_READONLY (parmdecl_map[REGNO (dest)]) = 0;
|
||
}
|
||
break;
|
||
|
||
#if 0 /* This is a good idea, but here is the wrong place for it. */
|
||
/* Arrange that CONST_INTs always appear as the second operand
|
||
if they appear, and that `frame_pointer_rtx' or `arg_pointer_rtx'
|
||
always appear as the first. */
|
||
case PLUS:
|
||
if (GET_CODE (XEXP (x, 0)) == CONST_INT
|
||
|| (XEXP (x, 1) == frame_pointer_rtx
|
||
|| (ARG_POINTER_REGNUM != FRAME_POINTER_REGNUM
|
||
&& XEXP (x, 1) == arg_pointer_rtx)))
|
||
{
|
||
rtx t = XEXP (x, 0);
|
||
XEXP (x, 0) = XEXP (x, 1);
|
||
XEXP (x, 1) = t;
|
||
}
|
||
break;
|
||
#endif
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Replace this rtx with a copy of itself. */
|
||
|
||
x = rtx_alloc (code);
|
||
bcopy ((char *) orig, (char *) x,
|
||
(sizeof (*x) - sizeof (x->fld)
|
||
+ sizeof (x->fld[0]) * GET_RTX_LENGTH (code)));
|
||
|
||
/* Now scan the subexpressions recursively.
|
||
We can store any replaced subexpressions directly into X
|
||
since we know X is not shared! Any vectors in X
|
||
must be copied if X was copied. */
|
||
|
||
format_ptr = GET_RTX_FORMAT (code);
|
||
|
||
for (i = 0; i < GET_RTX_LENGTH (code); i++)
|
||
{
|
||
switch (*format_ptr++)
|
||
{
|
||
case 'e':
|
||
XEXP (x, i) = copy_for_inline (XEXP (x, i));
|
||
break;
|
||
|
||
case 'u':
|
||
/* Change any references to old-insns to point to the
|
||
corresponding copied insns. */
|
||
XEXP (x, i) = insn_map[INSN_UID (XEXP (x, i))];
|
||
break;
|
||
|
||
case 'E':
|
||
if (XVEC (x, i) != NULL && XVECLEN (x, i) != 0)
|
||
{
|
||
register int j;
|
||
|
||
XVEC (x, i) = gen_rtvec_vv (XVECLEN (x, i), XVEC (x, i)->elem);
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
XVECEXP (x, i, j)
|
||
= copy_for_inline (XVECEXP (x, i, j));
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (code == ASM_OPERANDS && orig_asm_operands_vector == 0)
|
||
{
|
||
orig_asm_operands_vector = XVEC (orig, 3);
|
||
copy_asm_operands_vector = XVEC (x, 3);
|
||
copy_asm_constraints_vector = XVEC (x, 4);
|
||
}
|
||
|
||
return x;
|
||
}
|
||
|
||
/* Unfortunately, we need a global copy of const_equiv map for communication
|
||
with a function called from note_stores. Be *very* careful that this
|
||
is used properly in the presence of recursion. */
|
||
|
||
varray_type global_const_equiv_varray;
|
||
|
||
#define FIXED_BASE_PLUS_P(X) \
|
||
(GET_CODE (X) == PLUS && GET_CODE (XEXP (X, 1)) == CONST_INT \
|
||
&& GET_CODE (XEXP (X, 0)) == REG \
|
||
&& REGNO (XEXP (X, 0)) >= FIRST_VIRTUAL_REGISTER \
|
||
&& REGNO (XEXP (X, 0)) <= LAST_VIRTUAL_REGISTER)
|
||
|
||
/* Called to set up a mapping for the case where a parameter is in a
|
||
register. If it is read-only and our argument is a constant, set up the
|
||
constant equivalence.
|
||
|
||
If LOC is REG_USERVAR_P, the usual case, COPY must also have that flag set
|
||
if it is a register.
|
||
|
||
Also, don't allow hard registers here; they might not be valid when
|
||
substituted into insns. */
|
||
static void
|
||
process_reg_param (map, loc, copy)
|
||
struct inline_remap *map;
|
||
rtx loc, copy;
|
||
{
|
||
if ((GET_CODE (copy) != REG && GET_CODE (copy) != SUBREG)
|
||
|| (GET_CODE (copy) == REG && REG_USERVAR_P (loc)
|
||
&& ! REG_USERVAR_P (copy))
|
||
|| (GET_CODE (copy) == REG
|
||
&& REGNO (copy) < FIRST_PSEUDO_REGISTER))
|
||
{
|
||
rtx temp = copy_to_mode_reg (GET_MODE (loc), copy);
|
||
REG_USERVAR_P (temp) = REG_USERVAR_P (loc);
|
||
if (CONSTANT_P (copy) || FIXED_BASE_PLUS_P (copy))
|
||
SET_CONST_EQUIV_DATA (map, temp, copy, CONST_AGE_PARM);
|
||
copy = temp;
|
||
}
|
||
map->reg_map[REGNO (loc)] = copy;
|
||
}
|
||
|
||
/* Used by duplicate_eh_handlers to map labels for the exception table */
|
||
static struct inline_remap *eif_eh_map;
|
||
|
||
static rtx
|
||
expand_inline_function_eh_labelmap (label)
|
||
rtx label;
|
||
{
|
||
int index = CODE_LABEL_NUMBER (label);
|
||
return get_label_from_map (eif_eh_map, index);
|
||
}
|
||
|
||
/* Integrate the procedure defined by FNDECL. Note that this function
|
||
may wind up calling itself. Since the static variables are not
|
||
reentrant, we do not assign them until after the possibility
|
||
of recursion is eliminated.
|
||
|
||
If IGNORE is nonzero, do not produce a value.
|
||
Otherwise store the value in TARGET if it is nonzero and that is convenient.
|
||
|
||
Value is:
|
||
(rtx)-1 if we could not substitute the function
|
||
0 if we substituted it and it does not produce a value
|
||
else an rtx for where the value is stored. */
|
||
|
||
rtx
|
||
expand_inline_function (fndecl, parms, target, ignore, type,
|
||
structure_value_addr)
|
||
tree fndecl, parms;
|
||
rtx target;
|
||
int ignore;
|
||
tree type;
|
||
rtx structure_value_addr;
|
||
{
|
||
tree formal, actual, block;
|
||
rtx header = DECL_SAVED_INSNS (fndecl);
|
||
rtx insns = FIRST_FUNCTION_INSN (header);
|
||
rtx parm_insns = FIRST_PARM_INSN (header);
|
||
tree *arg_trees;
|
||
rtx *arg_vals;
|
||
rtx insn;
|
||
int max_regno;
|
||
register int i;
|
||
int min_labelno = FIRST_LABELNO (header);
|
||
int max_labelno = LAST_LABELNO (header);
|
||
int nargs;
|
||
rtx local_return_label = 0;
|
||
rtx loc;
|
||
rtx stack_save = 0;
|
||
rtx temp;
|
||
struct inline_remap *map = 0;
|
||
#ifdef HAVE_cc0
|
||
rtx cc0_insn = 0;
|
||
#endif
|
||
rtvec arg_vector = ORIGINAL_ARG_VECTOR (header);
|
||
rtx static_chain_value = 0;
|
||
|
||
/* The pointer used to track the true location of the memory used
|
||
for MAP->LABEL_MAP. */
|
||
rtx *real_label_map = 0;
|
||
|
||
/* Allow for equivalences of the pseudos we make for virtual fp and ap. */
|
||
max_regno = MAX_REGNUM (header) + 3;
|
||
if (max_regno < FIRST_PSEUDO_REGISTER)
|
||
abort ();
|
||
|
||
nargs = list_length (DECL_ARGUMENTS (fndecl));
|
||
|
||
/* Check that the parms type match and that sufficient arguments were
|
||
passed. Since the appropriate conversions or default promotions have
|
||
already been applied, the machine modes should match exactly. */
|
||
|
||
for (formal = DECL_ARGUMENTS (fndecl), actual = parms;
|
||
formal;
|
||
formal = TREE_CHAIN (formal), actual = TREE_CHAIN (actual))
|
||
{
|
||
tree arg;
|
||
enum machine_mode mode;
|
||
|
||
if (actual == 0)
|
||
return (rtx) (HOST_WIDE_INT) -1;
|
||
|
||
arg = TREE_VALUE (actual);
|
||
mode = TYPE_MODE (DECL_ARG_TYPE (formal));
|
||
|
||
if (mode != TYPE_MODE (TREE_TYPE (arg))
|
||
/* If they are block mode, the types should match exactly.
|
||
They don't match exactly if TREE_TYPE (FORMAL) == ERROR_MARK_NODE,
|
||
which could happen if the parameter has incomplete type. */
|
||
|| (mode == BLKmode
|
||
&& (TYPE_MAIN_VARIANT (TREE_TYPE (arg))
|
||
!= TYPE_MAIN_VARIANT (TREE_TYPE (formal)))))
|
||
return (rtx) (HOST_WIDE_INT) -1;
|
||
}
|
||
|
||
/* Extra arguments are valid, but will be ignored below, so we must
|
||
evaluate them here for side-effects. */
|
||
for (; actual; actual = TREE_CHAIN (actual))
|
||
expand_expr (TREE_VALUE (actual), const0_rtx,
|
||
TYPE_MODE (TREE_TYPE (TREE_VALUE (actual))), 0);
|
||
|
||
/* Make a binding contour to keep inline cleanups called at
|
||
outer function-scope level from looking like they are shadowing
|
||
parameter declarations. */
|
||
pushlevel (0);
|
||
|
||
/* Expand the function arguments. Do this first so that any
|
||
new registers get created before we allocate the maps. */
|
||
|
||
arg_vals = (rtx *) alloca (nargs * sizeof (rtx));
|
||
arg_trees = (tree *) alloca (nargs * sizeof (tree));
|
||
|
||
for (formal = DECL_ARGUMENTS (fndecl), actual = parms, i = 0;
|
||
formal;
|
||
formal = TREE_CHAIN (formal), actual = TREE_CHAIN (actual), i++)
|
||
{
|
||
/* Actual parameter, converted to the type of the argument within the
|
||
function. */
|
||
tree arg = convert (TREE_TYPE (formal), TREE_VALUE (actual));
|
||
/* Mode of the variable used within the function. */
|
||
enum machine_mode mode = TYPE_MODE (TREE_TYPE (formal));
|
||
int invisiref = 0;
|
||
|
||
arg_trees[i] = arg;
|
||
loc = RTVEC_ELT (arg_vector, i);
|
||
|
||
/* If this is an object passed by invisible reference, we copy the
|
||
object into a stack slot and save its address. If this will go
|
||
into memory, we do nothing now. Otherwise, we just expand the
|
||
argument. */
|
||
if (GET_CODE (loc) == MEM && GET_CODE (XEXP (loc, 0)) == REG
|
||
&& REGNO (XEXP (loc, 0)) > LAST_VIRTUAL_REGISTER)
|
||
{
|
||
rtx stack_slot
|
||
= assign_stack_temp (TYPE_MODE (TREE_TYPE (arg)),
|
||
int_size_in_bytes (TREE_TYPE (arg)), 1);
|
||
MEM_SET_IN_STRUCT_P (stack_slot,
|
||
AGGREGATE_TYPE_P (TREE_TYPE (arg)));
|
||
|
||
store_expr (arg, stack_slot, 0);
|
||
|
||
arg_vals[i] = XEXP (stack_slot, 0);
|
||
invisiref = 1;
|
||
}
|
||
else if (GET_CODE (loc) != MEM)
|
||
{
|
||
if (GET_MODE (loc) != TYPE_MODE (TREE_TYPE (arg)))
|
||
/* The mode if LOC and ARG can differ if LOC was a variable
|
||
that had its mode promoted via PROMOTED_MODE. */
|
||
arg_vals[i] = convert_modes (GET_MODE (loc),
|
||
TYPE_MODE (TREE_TYPE (arg)),
|
||
expand_expr (arg, NULL_RTX, mode,
|
||
EXPAND_SUM),
|
||
TREE_UNSIGNED (TREE_TYPE (formal)));
|
||
else
|
||
arg_vals[i] = expand_expr (arg, NULL_RTX, mode, EXPAND_SUM);
|
||
}
|
||
else
|
||
arg_vals[i] = 0;
|
||
|
||
if (arg_vals[i] != 0
|
||
&& (! TREE_READONLY (formal)
|
||
/* If the parameter is not read-only, copy our argument through
|
||
a register. Also, we cannot use ARG_VALS[I] if it overlaps
|
||
TARGET in any way. In the inline function, they will likely
|
||
be two different pseudos, and `safe_from_p' will make all
|
||
sorts of smart assumptions about their not conflicting.
|
||
But if ARG_VALS[I] overlaps TARGET, these assumptions are
|
||
wrong, so put ARG_VALS[I] into a fresh register.
|
||
Don't worry about invisible references, since their stack
|
||
temps will never overlap the target. */
|
||
|| (target != 0
|
||
&& ! invisiref
|
||
&& (GET_CODE (arg_vals[i]) == REG
|
||
|| GET_CODE (arg_vals[i]) == SUBREG
|
||
|| GET_CODE (arg_vals[i]) == MEM)
|
||
&& reg_overlap_mentioned_p (arg_vals[i], target))
|
||
/* ??? We must always copy a SUBREG into a REG, because it might
|
||
get substituted into an address, and not all ports correctly
|
||
handle SUBREGs in addresses. */
|
||
|| (GET_CODE (arg_vals[i]) == SUBREG)))
|
||
arg_vals[i] = copy_to_mode_reg (GET_MODE (loc), arg_vals[i]);
|
||
|
||
if (arg_vals[i] != 0 && GET_CODE (arg_vals[i]) == REG
|
||
&& POINTER_TYPE_P (TREE_TYPE (formal)))
|
||
mark_reg_pointer (arg_vals[i],
|
||
(TYPE_ALIGN (TREE_TYPE (TREE_TYPE (formal)))
|
||
/ BITS_PER_UNIT));
|
||
}
|
||
|
||
/* Allocate the structures we use to remap things. */
|
||
|
||
map = (struct inline_remap *) alloca (sizeof (struct inline_remap));
|
||
map->fndecl = fndecl;
|
||
|
||
map->reg_map = (rtx *) alloca (max_regno * sizeof (rtx));
|
||
bzero ((char *) map->reg_map, max_regno * sizeof (rtx));
|
||
|
||
/* We used to use alloca here, but the size of what it would try to
|
||
allocate would occasionally cause it to exceed the stack limit and
|
||
cause unpredictable core dumps. */
|
||
real_label_map
|
||
= (rtx *) xmalloc ((max_labelno) * sizeof (rtx));
|
||
map->label_map = real_label_map;
|
||
|
||
map->insn_map = (rtx *) alloca (INSN_UID (header) * sizeof (rtx));
|
||
bzero ((char *) map->insn_map, INSN_UID (header) * sizeof (rtx));
|
||
map->min_insnno = 0;
|
||
map->max_insnno = INSN_UID (header);
|
||
|
||
map->integrating = 1;
|
||
|
||
/* const_equiv_varray maps pseudos in our routine to constants, so
|
||
it needs to be large enough for all our pseudos. This is the
|
||
number we are currently using plus the number in the called
|
||
routine, plus 15 for each arg, five to compute the virtual frame
|
||
pointer, and five for the return value. This should be enough
|
||
for most cases. We do not reference entries outside the range of
|
||
the map.
|
||
|
||
??? These numbers are quite arbitrary and were obtained by
|
||
experimentation. At some point, we should try to allocate the
|
||
table after all the parameters are set up so we an more accurately
|
||
estimate the number of pseudos we will need. */
|
||
|
||
VARRAY_CONST_EQUIV_INIT (map->const_equiv_varray,
|
||
(max_reg_num ()
|
||
+ (max_regno - FIRST_PSEUDO_REGISTER)
|
||
+ 15 * nargs
|
||
+ 10),
|
||
"expand_inline_function");
|
||
map->const_age = 0;
|
||
|
||
/* Record the current insn in case we have to set up pointers to frame
|
||
and argument memory blocks. If there are no insns yet, add a dummy
|
||
insn that can be used as an insertion point. */
|
||
map->insns_at_start = get_last_insn ();
|
||
if (map->insns_at_start == 0)
|
||
map->insns_at_start = emit_note (NULL_PTR, NOTE_INSN_DELETED);
|
||
|
||
map->regno_pointer_flag = INLINE_REGNO_POINTER_FLAG (header);
|
||
map->regno_pointer_align = INLINE_REGNO_POINTER_ALIGN (header);
|
||
|
||
/* Update the outgoing argument size to allow for those in the inlined
|
||
function. */
|
||
if (OUTGOING_ARGS_SIZE (header) > current_function_outgoing_args_size)
|
||
current_function_outgoing_args_size = OUTGOING_ARGS_SIZE (header);
|
||
|
||
/* If the inline function needs to make PIC references, that means
|
||
that this function's PIC offset table must be used. */
|
||
if (FUNCTION_FLAGS (header) & FUNCTION_FLAGS_USES_PIC_OFFSET_TABLE)
|
||
current_function_uses_pic_offset_table = 1;
|
||
|
||
/* If this function needs a context, set it up. */
|
||
if (FUNCTION_FLAGS (header) & FUNCTION_FLAGS_NEEDS_CONTEXT)
|
||
static_chain_value = lookup_static_chain (fndecl);
|
||
|
||
if (GET_CODE (parm_insns) == NOTE
|
||
&& NOTE_LINE_NUMBER (parm_insns) > 0)
|
||
{
|
||
rtx note = emit_note (NOTE_SOURCE_FILE (parm_insns),
|
||
NOTE_LINE_NUMBER (parm_insns));
|
||
if (note)
|
||
RTX_INTEGRATED_P (note) = 1;
|
||
}
|
||
|
||
/* Process each argument. For each, set up things so that the function's
|
||
reference to the argument will refer to the argument being passed.
|
||
We only replace REG with REG here. Any simplifications are done
|
||
via const_equiv_map.
|
||
|
||
We make two passes: In the first, we deal with parameters that will
|
||
be placed into registers, since we need to ensure that the allocated
|
||
register number fits in const_equiv_map. Then we store all non-register
|
||
parameters into their memory location. */
|
||
|
||
/* Don't try to free temp stack slots here, because we may put one of the
|
||
parameters into a temp stack slot. */
|
||
|
||
for (i = 0; i < nargs; i++)
|
||
{
|
||
rtx copy = arg_vals[i];
|
||
|
||
loc = RTVEC_ELT (arg_vector, i);
|
||
|
||
/* There are three cases, each handled separately. */
|
||
if (GET_CODE (loc) == MEM && GET_CODE (XEXP (loc, 0)) == REG
|
||
&& REGNO (XEXP (loc, 0)) > LAST_VIRTUAL_REGISTER)
|
||
{
|
||
/* This must be an object passed by invisible reference (it could
|
||
also be a variable-sized object, but we forbid inlining functions
|
||
with variable-sized arguments). COPY is the address of the
|
||
actual value (this computation will cause it to be copied). We
|
||
map that address for the register, noting the actual address as
|
||
an equivalent in case it can be substituted into the insns. */
|
||
|
||
if (GET_CODE (copy) != REG)
|
||
{
|
||
temp = copy_addr_to_reg (copy);
|
||
if (CONSTANT_P (copy) || FIXED_BASE_PLUS_P (copy))
|
||
SET_CONST_EQUIV_DATA (map, temp, copy, CONST_AGE_PARM);
|
||
copy = temp;
|
||
}
|
||
map->reg_map[REGNO (XEXP (loc, 0))] = copy;
|
||
}
|
||
else if (GET_CODE (loc) == MEM)
|
||
{
|
||
/* This is the case of a parameter that lives in memory.
|
||
It will live in the block we allocate in the called routine's
|
||
frame that simulates the incoming argument area. Do nothing
|
||
now; we will call store_expr later. */
|
||
;
|
||
}
|
||
else if (GET_CODE (loc) == REG)
|
||
process_reg_param (map, loc, copy);
|
||
else if (GET_CODE (loc) == CONCAT)
|
||
{
|
||
rtx locreal = gen_realpart (GET_MODE (XEXP (loc, 0)), loc);
|
||
rtx locimag = gen_imagpart (GET_MODE (XEXP (loc, 0)), loc);
|
||
rtx copyreal = gen_realpart (GET_MODE (locreal), copy);
|
||
rtx copyimag = gen_imagpart (GET_MODE (locimag), copy);
|
||
|
||
process_reg_param (map, locreal, copyreal);
|
||
process_reg_param (map, locimag, copyimag);
|
||
}
|
||
else
|
||
abort ();
|
||
}
|
||
|
||
/* Now do the parameters that will be placed in memory. */
|
||
|
||
for (formal = DECL_ARGUMENTS (fndecl), i = 0;
|
||
formal; formal = TREE_CHAIN (formal), i++)
|
||
{
|
||
loc = RTVEC_ELT (arg_vector, i);
|
||
|
||
if (GET_CODE (loc) == MEM
|
||
/* Exclude case handled above. */
|
||
&& ! (GET_CODE (XEXP (loc, 0)) == REG
|
||
&& REGNO (XEXP (loc, 0)) > LAST_VIRTUAL_REGISTER))
|
||
{
|
||
rtx note = emit_note (DECL_SOURCE_FILE (formal),
|
||
DECL_SOURCE_LINE (formal));
|
||
if (note)
|
||
RTX_INTEGRATED_P (note) = 1;
|
||
|
||
/* Compute the address in the area we reserved and store the
|
||
value there. */
|
||
temp = copy_rtx_and_substitute (loc, map);
|
||
subst_constants (&temp, NULL_RTX, map);
|
||
apply_change_group ();
|
||
if (! memory_address_p (GET_MODE (temp), XEXP (temp, 0)))
|
||
temp = change_address (temp, VOIDmode, XEXP (temp, 0));
|
||
store_expr (arg_trees[i], temp, 0);
|
||
}
|
||
}
|
||
|
||
/* Deal with the places that the function puts its result.
|
||
We are driven by what is placed into DECL_RESULT.
|
||
|
||
Initially, we assume that we don't have anything special handling for
|
||
REG_FUNCTION_RETURN_VALUE_P. */
|
||
|
||
map->inline_target = 0;
|
||
loc = DECL_RTL (DECL_RESULT (fndecl));
|
||
|
||
if (TYPE_MODE (type) == VOIDmode)
|
||
/* There is no return value to worry about. */
|
||
;
|
||
else if (GET_CODE (loc) == MEM)
|
||
{
|
||
if (GET_CODE (XEXP (loc, 0)) == ADDRESSOF)
|
||
{
|
||
temp = copy_rtx_and_substitute (loc, map);
|
||
subst_constants (&temp, NULL_RTX, map);
|
||
apply_change_group ();
|
||
target = temp;
|
||
}
|
||
else
|
||
{
|
||
if (! structure_value_addr
|
||
|| ! aggregate_value_p (DECL_RESULT (fndecl)))
|
||
abort ();
|
||
|
||
/* Pass the function the address in which to return a structure
|
||
value. Note that a constructor can cause someone to call us
|
||
with STRUCTURE_VALUE_ADDR, but the initialization takes place
|
||
via the first parameter, rather than the struct return address.
|
||
|
||
We have two cases: If the address is a simple register
|
||
indirect, use the mapping mechanism to point that register to
|
||
our structure return address. Otherwise, store the structure
|
||
return value into the place that it will be referenced from. */
|
||
|
||
if (GET_CODE (XEXP (loc, 0)) == REG)
|
||
{
|
||
temp = force_operand (structure_value_addr, NULL_RTX);
|
||
temp = force_reg (Pmode, temp);
|
||
map->reg_map[REGNO (XEXP (loc, 0))] = temp;
|
||
|
||
if (CONSTANT_P (structure_value_addr)
|
||
|| GET_CODE (structure_value_addr) == ADDRESSOF
|
||
|| (GET_CODE (structure_value_addr) == PLUS
|
||
&& (XEXP (structure_value_addr, 0)
|
||
== virtual_stack_vars_rtx)
|
||
&& (GET_CODE (XEXP (structure_value_addr, 1))
|
||
== CONST_INT)))
|
||
{
|
||
SET_CONST_EQUIV_DATA (map, temp, structure_value_addr,
|
||
CONST_AGE_PARM);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
temp = copy_rtx_and_substitute (loc, map);
|
||
subst_constants (&temp, NULL_RTX, map);
|
||
apply_change_group ();
|
||
emit_move_insn (temp, structure_value_addr);
|
||
}
|
||
}
|
||
}
|
||
else if (ignore)
|
||
/* We will ignore the result value, so don't look at its structure.
|
||
Note that preparations for an aggregate return value
|
||
do need to be made (above) even if it will be ignored. */
|
||
;
|
||
else if (GET_CODE (loc) == REG)
|
||
{
|
||
/* The function returns an object in a register and we use the return
|
||
value. Set up our target for remapping. */
|
||
|
||
/* Machine mode function was declared to return. */
|
||
enum machine_mode departing_mode = TYPE_MODE (type);
|
||
/* (Possibly wider) machine mode it actually computes
|
||
(for the sake of callers that fail to declare it right).
|
||
We have to use the mode of the result's RTL, rather than
|
||
its type, since expand_function_start may have promoted it. */
|
||
enum machine_mode arriving_mode
|
||
= GET_MODE (DECL_RTL (DECL_RESULT (fndecl)));
|
||
rtx reg_to_map;
|
||
|
||
/* Don't use MEMs as direct targets because on some machines
|
||
substituting a MEM for a REG makes invalid insns.
|
||
Let the combiner substitute the MEM if that is valid. */
|
||
if (target == 0 || GET_CODE (target) != REG
|
||
|| GET_MODE (target) != departing_mode)
|
||
{
|
||
/* Don't make BLKmode registers. If this looks like
|
||
a BLKmode object being returned in a register, get
|
||
the mode from that, otherwise abort. */
|
||
if (departing_mode == BLKmode)
|
||
{
|
||
if (REG == GET_CODE (DECL_RTL (DECL_RESULT (fndecl))))
|
||
{
|
||
departing_mode = GET_MODE (DECL_RTL (DECL_RESULT (fndecl)));
|
||
arriving_mode = departing_mode;
|
||
}
|
||
else
|
||
abort();
|
||
}
|
||
|
||
target = gen_reg_rtx (departing_mode);
|
||
}
|
||
|
||
/* If function's value was promoted before return,
|
||
avoid machine mode mismatch when we substitute INLINE_TARGET.
|
||
But TARGET is what we will return to the caller. */
|
||
if (arriving_mode != departing_mode)
|
||
{
|
||
/* Avoid creating a paradoxical subreg wider than
|
||
BITS_PER_WORD, since that is illegal. */
|
||
if (GET_MODE_BITSIZE (arriving_mode) > BITS_PER_WORD)
|
||
{
|
||
if (!TRULY_NOOP_TRUNCATION (GET_MODE_BITSIZE (departing_mode),
|
||
GET_MODE_BITSIZE (arriving_mode)))
|
||
/* Maybe could be handled by using convert_move () ? */
|
||
abort ();
|
||
reg_to_map = gen_reg_rtx (arriving_mode);
|
||
target = gen_lowpart (departing_mode, reg_to_map);
|
||
}
|
||
else
|
||
reg_to_map = gen_rtx_SUBREG (arriving_mode, target, 0);
|
||
}
|
||
else
|
||
reg_to_map = target;
|
||
|
||
/* Usually, the result value is the machine's return register.
|
||
Sometimes it may be a pseudo. Handle both cases. */
|
||
if (REG_FUNCTION_VALUE_P (loc))
|
||
map->inline_target = reg_to_map;
|
||
else
|
||
map->reg_map[REGNO (loc)] = reg_to_map;
|
||
}
|
||
else
|
||
abort ();
|
||
|
||
/* Make a fresh binding contour that we can easily remove. Do this after
|
||
expanding our arguments so cleanups are properly scoped. */
|
||
pushlevel (0);
|
||
expand_start_bindings (0);
|
||
|
||
/* Initialize label_map. get_label_from_map will actually make
|
||
the labels. */
|
||
bzero ((char *) &map->label_map [min_labelno],
|
||
(max_labelno - min_labelno) * sizeof (rtx));
|
||
|
||
/* Perform postincrements before actually calling the function. */
|
||
emit_queue ();
|
||
|
||
/* Clean up stack so that variables might have smaller offsets. */
|
||
do_pending_stack_adjust ();
|
||
|
||
/* Save a copy of the location of const_equiv_varray for
|
||
mark_stores, called via note_stores. */
|
||
global_const_equiv_varray = map->const_equiv_varray;
|
||
|
||
/* If the called function does an alloca, save and restore the
|
||
stack pointer around the call. This saves stack space, but
|
||
also is required if this inline is being done between two
|
||
pushes. */
|
||
if (FUNCTION_FLAGS (header) & FUNCTION_FLAGS_CALLS_ALLOCA)
|
||
emit_stack_save (SAVE_BLOCK, &stack_save, NULL_RTX);
|
||
|
||
/* Now copy the insns one by one. Do this in two passes, first the insns and
|
||
then their REG_NOTES, just like save_for_inline. */
|
||
|
||
/* This loop is very similar to the loop in copy_loop_body in unroll.c. */
|
||
|
||
for (insn = insns; insn; insn = NEXT_INSN (insn))
|
||
{
|
||
rtx copy, pattern, set;
|
||
|
||
map->orig_asm_operands_vector = 0;
|
||
|
||
switch (GET_CODE (insn))
|
||
{
|
||
case INSN:
|
||
pattern = PATTERN (insn);
|
||
set = single_set (insn);
|
||
copy = 0;
|
||
if (GET_CODE (pattern) == USE
|
||
&& GET_CODE (XEXP (pattern, 0)) == REG
|
||
&& REG_FUNCTION_VALUE_P (XEXP (pattern, 0)))
|
||
/* The (USE (REG n)) at return from the function should
|
||
be ignored since we are changing (REG n) into
|
||
inline_target. */
|
||
break;
|
||
|
||
/* If the inline fn needs eh context, make sure that
|
||
the current fn has one. */
|
||
if (GET_CODE (pattern) == USE
|
||
&& find_reg_note (insn, REG_EH_CONTEXT, 0) != 0)
|
||
get_eh_context ();
|
||
|
||
/* Ignore setting a function value that we don't want to use. */
|
||
if (map->inline_target == 0
|
||
&& set != 0
|
||
&& GET_CODE (SET_DEST (set)) == REG
|
||
&& REG_FUNCTION_VALUE_P (SET_DEST (set)))
|
||
{
|
||
if (volatile_refs_p (SET_SRC (set)))
|
||
{
|
||
rtx new_set;
|
||
|
||
/* If we must not delete the source,
|
||
load it into a new temporary. */
|
||
copy = emit_insn (copy_rtx_and_substitute (pattern, map));
|
||
|
||
new_set = single_set (copy);
|
||
if (new_set == 0)
|
||
abort ();
|
||
|
||
SET_DEST (new_set)
|
||
= gen_reg_rtx (GET_MODE (SET_DEST (new_set)));
|
||
}
|
||
/* If the source and destination are the same and it
|
||
has a note on it, keep the insn. */
|
||
else if (rtx_equal_p (SET_DEST (set), SET_SRC (set))
|
||
&& REG_NOTES (insn) != 0)
|
||
copy = emit_insn (copy_rtx_and_substitute (pattern, map));
|
||
else
|
||
break;
|
||
}
|
||
|
||
/* If this is setting the static chain rtx, omit it. */
|
||
else if (static_chain_value != 0
|
||
&& set != 0
|
||
&& GET_CODE (SET_DEST (set)) == REG
|
||
&& rtx_equal_p (SET_DEST (set),
|
||
static_chain_incoming_rtx))
|
||
break;
|
||
|
||
/* If this is setting the static chain pseudo, set it from
|
||
the value we want to give it instead. */
|
||
else if (static_chain_value != 0
|
||
&& set != 0
|
||
&& rtx_equal_p (SET_SRC (set),
|
||
static_chain_incoming_rtx))
|
||
{
|
||
rtx newdest = copy_rtx_and_substitute (SET_DEST (set), map);
|
||
|
||
copy = emit_move_insn (newdest, static_chain_value);
|
||
static_chain_value = 0;
|
||
}
|
||
else
|
||
copy = emit_insn (copy_rtx_and_substitute (pattern, map));
|
||
/* REG_NOTES will be copied later. */
|
||
|
||
#ifdef HAVE_cc0
|
||
/* If this insn is setting CC0, it may need to look at
|
||
the insn that uses CC0 to see what type of insn it is.
|
||
In that case, the call to recog via validate_change will
|
||
fail. So don't substitute constants here. Instead,
|
||
do it when we emit the following insn.
|
||
|
||
For example, see the pyr.md file. That machine has signed and
|
||
unsigned compares. The compare patterns must check the
|
||
following branch insn to see which what kind of compare to
|
||
emit.
|
||
|
||
If the previous insn set CC0, substitute constants on it as
|
||
well. */
|
||
if (sets_cc0_p (PATTERN (copy)) != 0)
|
||
cc0_insn = copy;
|
||
else
|
||
{
|
||
if (cc0_insn)
|
||
try_constants (cc0_insn, map);
|
||
cc0_insn = 0;
|
||
try_constants (copy, map);
|
||
}
|
||
#else
|
||
try_constants (copy, map);
|
||
#endif
|
||
break;
|
||
|
||
case JUMP_INSN:
|
||
if (GET_CODE (PATTERN (insn)) == RETURN
|
||
|| (GET_CODE (PATTERN (insn)) == PARALLEL
|
||
&& GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == RETURN))
|
||
{
|
||
if (local_return_label == 0)
|
||
local_return_label = gen_label_rtx ();
|
||
pattern = gen_jump (local_return_label);
|
||
}
|
||
else
|
||
pattern = copy_rtx_and_substitute (PATTERN (insn), map);
|
||
|
||
copy = emit_jump_insn (pattern);
|
||
|
||
#ifdef HAVE_cc0
|
||
if (cc0_insn)
|
||
try_constants (cc0_insn, map);
|
||
cc0_insn = 0;
|
||
#endif
|
||
try_constants (copy, map);
|
||
|
||
/* If this used to be a conditional jump insn but whose branch
|
||
direction is now know, we must do something special. */
|
||
if (condjump_p (insn) && ! simplejump_p (insn) && map->last_pc_value)
|
||
{
|
||
#ifdef HAVE_cc0
|
||
/* The previous insn set cc0 for us. So delete it. */
|
||
delete_insn (PREV_INSN (copy));
|
||
#endif
|
||
|
||
/* If this is now a no-op, delete it. */
|
||
if (map->last_pc_value == pc_rtx)
|
||
{
|
||
delete_insn (copy);
|
||
copy = 0;
|
||
}
|
||
else
|
||
/* Otherwise, this is unconditional jump so we must put a
|
||
BARRIER after it. We could do some dead code elimination
|
||
here, but jump.c will do it just as well. */
|
||
emit_barrier ();
|
||
}
|
||
break;
|
||
|
||
case CALL_INSN:
|
||
pattern = copy_rtx_and_substitute (PATTERN (insn), map);
|
||
copy = emit_call_insn (pattern);
|
||
|
||
/* Because the USAGE information potentially contains objects other
|
||
than hard registers, we need to copy it. */
|
||
CALL_INSN_FUNCTION_USAGE (copy)
|
||
= copy_rtx_and_substitute (CALL_INSN_FUNCTION_USAGE (insn), map);
|
||
|
||
#ifdef HAVE_cc0
|
||
if (cc0_insn)
|
||
try_constants (cc0_insn, map);
|
||
cc0_insn = 0;
|
||
#endif
|
||
try_constants (copy, map);
|
||
|
||
/* Be lazy and assume CALL_INSNs clobber all hard registers. */
|
||
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
|
||
VARRAY_CONST_EQUIV (map->const_equiv_varray, i).rtx = 0;
|
||
break;
|
||
|
||
case CODE_LABEL:
|
||
copy = emit_label (get_label_from_map (map,
|
||
CODE_LABEL_NUMBER (insn)));
|
||
LABEL_NAME (copy) = LABEL_NAME (insn);
|
||
map->const_age++;
|
||
break;
|
||
|
||
case BARRIER:
|
||
copy = emit_barrier ();
|
||
break;
|
||
|
||
case NOTE:
|
||
/* It is important to discard function-end and function-beg notes,
|
||
so we have only one of each in the current function.
|
||
Also, NOTE_INSN_DELETED notes aren't useful (save_for_inline
|
||
deleted these in the copy used for continuing compilation,
|
||
not the copy used for inlining). */
|
||
if (NOTE_LINE_NUMBER (insn) != NOTE_INSN_FUNCTION_END
|
||
&& NOTE_LINE_NUMBER (insn) != NOTE_INSN_FUNCTION_BEG
|
||
&& NOTE_LINE_NUMBER (insn) != NOTE_INSN_DELETED)
|
||
{
|
||
copy = emit_note (NOTE_SOURCE_FILE (insn),
|
||
NOTE_LINE_NUMBER (insn));
|
||
if (copy
|
||
&& (NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_BEG
|
||
|| NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_END))
|
||
{
|
||
rtx label
|
||
= get_label_from_map (map, NOTE_BLOCK_NUMBER (copy));
|
||
|
||
/* we have to duplicate the handlers for the original */
|
||
if (NOTE_LINE_NUMBER (copy) == NOTE_INSN_EH_REGION_BEG)
|
||
{
|
||
/* We need to duplicate the handlers for the EH region
|
||
and we need to indicate where the label map is */
|
||
eif_eh_map = map;
|
||
duplicate_eh_handlers (NOTE_BLOCK_NUMBER (copy),
|
||
CODE_LABEL_NUMBER (label),
|
||
expand_inline_function_eh_labelmap);
|
||
}
|
||
|
||
/* We have to forward these both to match the new exception
|
||
region. */
|
||
NOTE_BLOCK_NUMBER (copy) = CODE_LABEL_NUMBER (label);
|
||
}
|
||
}
|
||
else
|
||
copy = 0;
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
break;
|
||
}
|
||
|
||
if (copy)
|
||
RTX_INTEGRATED_P (copy) = 1;
|
||
|
||
map->insn_map[INSN_UID (insn)] = copy;
|
||
}
|
||
|
||
/* Now copy the REG_NOTES. Increment const_age, so that only constants
|
||
from parameters can be substituted in. These are the only ones that
|
||
are valid across the entire function. */
|
||
map->const_age++;
|
||
for (insn = insns; insn; insn = NEXT_INSN (insn))
|
||
if (GET_RTX_CLASS (GET_CODE (insn)) == 'i'
|
||
&& map->insn_map[INSN_UID (insn)]
|
||
&& REG_NOTES (insn))
|
||
{
|
||
rtx tem = copy_rtx_and_substitute (REG_NOTES (insn), map);
|
||
/* We must also do subst_constants, in case one of our parameters
|
||
has const type and constant value. */
|
||
subst_constants (&tem, NULL_RTX, map);
|
||
apply_change_group ();
|
||
REG_NOTES (map->insn_map[INSN_UID (insn)]) = tem;
|
||
}
|
||
|
||
if (local_return_label)
|
||
emit_label (local_return_label);
|
||
|
||
/* Restore the stack pointer if we saved it above. */
|
||
if (FUNCTION_FLAGS (header) & FUNCTION_FLAGS_CALLS_ALLOCA)
|
||
emit_stack_restore (SAVE_BLOCK, stack_save, NULL_RTX);
|
||
|
||
/* Make copies of the decls of the symbols in the inline function, so that
|
||
the copies of the variables get declared in the current function. Set
|
||
up things so that lookup_static_chain knows that to interpret registers
|
||
in SAVE_EXPRs for TYPE_SIZEs as local. */
|
||
|
||
inline_function_decl = fndecl;
|
||
integrate_parm_decls (DECL_ARGUMENTS (fndecl), map, arg_vector);
|
||
integrate_decl_tree ((tree) ORIGINAL_DECL_INITIAL (header), 0, map);
|
||
inline_function_decl = 0;
|
||
|
||
/* End the scope containing the copied formal parameter variables
|
||
and copied LABEL_DECLs. */
|
||
|
||
expand_end_bindings (getdecls (), 1, 1);
|
||
block = poplevel (1, 1, 0);
|
||
BLOCK_ABSTRACT_ORIGIN (block) = (DECL_ABSTRACT_ORIGIN (fndecl) == NULL
|
||
? fndecl : DECL_ABSTRACT_ORIGIN (fndecl));
|
||
poplevel (0, 0, 0);
|
||
|
||
/* Must mark the line number note after inlined functions as a repeat, so
|
||
that the test coverage code can avoid counting the call twice. This
|
||
just tells the code to ignore the immediately following line note, since
|
||
there already exists a copy of this note before the expanded inline call.
|
||
This line number note is still needed for debugging though, so we can't
|
||
delete it. */
|
||
if (flag_test_coverage)
|
||
emit_note (0, NOTE_REPEATED_LINE_NUMBER);
|
||
|
||
emit_line_note (input_filename, lineno);
|
||
|
||
/* If the function returns a BLKmode object in a register, copy it
|
||
out of the temp register into a BLKmode memory object. */
|
||
if (TYPE_MODE (TREE_TYPE (TREE_TYPE (fndecl))) == BLKmode
|
||
&& ! aggregate_value_p (TREE_TYPE (TREE_TYPE (fndecl))))
|
||
target = copy_blkmode_from_reg (0, target, TREE_TYPE (TREE_TYPE (fndecl)));
|
||
|
||
if (structure_value_addr)
|
||
{
|
||
target = gen_rtx_MEM (TYPE_MODE (type),
|
||
memory_address (TYPE_MODE (type),
|
||
structure_value_addr));
|
||
MEM_SET_IN_STRUCT_P (target, 1);
|
||
}
|
||
|
||
/* Make sure we free the things we explicitly allocated with xmalloc. */
|
||
if (real_label_map)
|
||
free (real_label_map);
|
||
if (map)
|
||
VARRAY_FREE (map->const_equiv_varray);
|
||
|
||
return target;
|
||
}
|
||
|
||
/* Given a chain of PARM_DECLs, ARGS, copy each decl into a VAR_DECL,
|
||
push all of those decls and give each one the corresponding home. */
|
||
|
||
static void
|
||
integrate_parm_decls (args, map, arg_vector)
|
||
tree args;
|
||
struct inline_remap *map;
|
||
rtvec arg_vector;
|
||
{
|
||
register tree tail;
|
||
register int i;
|
||
|
||
for (tail = args, i = 0; tail; tail = TREE_CHAIN (tail), i++)
|
||
{
|
||
register tree decl = build_decl (VAR_DECL, DECL_NAME (tail),
|
||
TREE_TYPE (tail));
|
||
rtx new_decl_rtl
|
||
= copy_rtx_and_substitute (RTVEC_ELT (arg_vector, i), map);
|
||
|
||
DECL_ARG_TYPE (decl) = DECL_ARG_TYPE (tail);
|
||
/* We really should be setting DECL_INCOMING_RTL to something reasonable
|
||
here, but that's going to require some more work. */
|
||
/* DECL_INCOMING_RTL (decl) = ?; */
|
||
/* These args would always appear unused, if not for this. */
|
||
TREE_USED (decl) = 1;
|
||
/* Prevent warning for shadowing with these. */
|
||
DECL_ABSTRACT_ORIGIN (decl) = DECL_ORIGIN (tail);
|
||
pushdecl (decl);
|
||
/* Fully instantiate the address with the equivalent form so that the
|
||
debugging information contains the actual register, instead of the
|
||
virtual register. Do this by not passing an insn to
|
||
subst_constants. */
|
||
subst_constants (&new_decl_rtl, NULL_RTX, map);
|
||
apply_change_group ();
|
||
DECL_RTL (decl) = new_decl_rtl;
|
||
}
|
||
}
|
||
|
||
/* Given a BLOCK node LET, push decls and levels so as to construct in the
|
||
current function a tree of contexts isomorphic to the one that is given.
|
||
|
||
LEVEL indicates how far down into the BLOCK tree is the node we are
|
||
currently traversing. It is always zero except for recursive calls.
|
||
|
||
MAP, if nonzero, is a pointer to an inline_remap map which indicates how
|
||
registers used in the DECL_RTL field should be remapped. If it is zero,
|
||
no mapping is necessary. */
|
||
|
||
static void
|
||
integrate_decl_tree (let, level, map)
|
||
tree let;
|
||
int level;
|
||
struct inline_remap *map;
|
||
{
|
||
tree t, node;
|
||
|
||
if (level > 0)
|
||
pushlevel (0);
|
||
|
||
for (t = BLOCK_VARS (let); t; t = TREE_CHAIN (t))
|
||
{
|
||
tree d;
|
||
|
||
push_obstacks_nochange ();
|
||
saveable_allocation ();
|
||
d = copy_and_set_decl_abstract_origin (t);
|
||
pop_obstacks ();
|
||
|
||
if (DECL_RTL (t) != 0)
|
||
{
|
||
DECL_RTL (d) = copy_rtx_and_substitute (DECL_RTL (t), map);
|
||
/* Fully instantiate the address with the equivalent form so that the
|
||
debugging information contains the actual register, instead of the
|
||
virtual register. Do this by not passing an insn to
|
||
subst_constants. */
|
||
subst_constants (&DECL_RTL (d), NULL_RTX, map);
|
||
apply_change_group ();
|
||
}
|
||
/* These args would always appear unused, if not for this. */
|
||
TREE_USED (d) = 1;
|
||
|
||
if (DECL_LANG_SPECIFIC (d))
|
||
copy_lang_decl (d);
|
||
|
||
pushdecl (d);
|
||
}
|
||
|
||
for (t = BLOCK_SUBBLOCKS (let); t; t = TREE_CHAIN (t))
|
||
integrate_decl_tree (t, level + 1, map);
|
||
|
||
if (level > 0)
|
||
{
|
||
node = poplevel (1, 0, 0);
|
||
if (node)
|
||
{
|
||
TREE_USED (node) = TREE_USED (let);
|
||
BLOCK_ABSTRACT_ORIGIN (node) = let;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Given a BLOCK node LET, search for all DECL_RTL fields, and pass them
|
||
through save_constants. */
|
||
|
||
static void
|
||
save_constants_in_decl_trees (let)
|
||
tree let;
|
||
{
|
||
tree t;
|
||
|
||
for (t = BLOCK_VARS (let); t; t = TREE_CHAIN (t))
|
||
if (DECL_RTL (t) != 0)
|
||
save_constants (&DECL_RTL (t));
|
||
|
||
for (t = BLOCK_SUBBLOCKS (let); t; t = TREE_CHAIN (t))
|
||
save_constants_in_decl_trees (t);
|
||
}
|
||
|
||
/* Create a new copy of an rtx.
|
||
Recursively copies the operands of the rtx,
|
||
except for those few rtx codes that are sharable.
|
||
|
||
We always return an rtx that is similar to that incoming rtx, with the
|
||
exception of possibly changing a REG to a SUBREG or vice versa. No
|
||
rtl is ever emitted.
|
||
|
||
Handle constants that need to be placed in the constant pool by
|
||
calling `force_const_mem'. */
|
||
|
||
rtx
|
||
copy_rtx_and_substitute (orig, map)
|
||
register rtx orig;
|
||
struct inline_remap *map;
|
||
{
|
||
register rtx copy, temp;
|
||
register int i, j;
|
||
register RTX_CODE code;
|
||
register enum machine_mode mode;
|
||
register char *format_ptr;
|
||
int regno;
|
||
|
||
if (orig == 0)
|
||
return 0;
|
||
|
||
code = GET_CODE (orig);
|
||
mode = GET_MODE (orig);
|
||
|
||
switch (code)
|
||
{
|
||
case REG:
|
||
/* If the stack pointer register shows up, it must be part of
|
||
stack-adjustments (*not* because we eliminated the frame pointer!).
|
||
Small hard registers are returned as-is. Pseudo-registers
|
||
go through their `reg_map'. */
|
||
regno = REGNO (orig);
|
||
if (regno <= LAST_VIRTUAL_REGISTER)
|
||
{
|
||
/* Some hard registers are also mapped,
|
||
but others are not translated. */
|
||
if (map->reg_map[regno] != 0)
|
||
return map->reg_map[regno];
|
||
|
||
/* If this is the virtual frame pointer, make space in current
|
||
function's stack frame for the stack frame of the inline function.
|
||
|
||
Copy the address of this area into a pseudo. Map
|
||
virtual_stack_vars_rtx to this pseudo and set up a constant
|
||
equivalence for it to be the address. This will substitute the
|
||
address into insns where it can be substituted and use the new
|
||
pseudo where it can't. */
|
||
if (regno == VIRTUAL_STACK_VARS_REGNUM)
|
||
{
|
||
rtx loc, seq;
|
||
int size = DECL_FRAME_SIZE (map->fndecl);
|
||
|
||
#ifdef FRAME_GROWS_DOWNWARD
|
||
/* In this case, virtual_stack_vars_rtx points to one byte
|
||
higher than the top of the frame area. So make sure we
|
||
allocate a big enough chunk to keep the frame pointer
|
||
aligned like a real one. */
|
||
size = CEIL_ROUND (size, BIGGEST_ALIGNMENT / BITS_PER_UNIT);
|
||
#endif
|
||
start_sequence ();
|
||
loc = assign_stack_temp (BLKmode, size, 1);
|
||
loc = XEXP (loc, 0);
|
||
#ifdef FRAME_GROWS_DOWNWARD
|
||
/* In this case, virtual_stack_vars_rtx points to one byte
|
||
higher than the top of the frame area. So compute the offset
|
||
to one byte higher than our substitute frame. */
|
||
loc = plus_constant (loc, size);
|
||
#endif
|
||
map->reg_map[regno] = temp
|
||
= force_reg (Pmode, force_operand (loc, NULL_RTX));
|
||
|
||
#ifdef STACK_BOUNDARY
|
||
mark_reg_pointer (map->reg_map[regno],
|
||
STACK_BOUNDARY / BITS_PER_UNIT);
|
||
#endif
|
||
|
||
SET_CONST_EQUIV_DATA (map, temp, loc, CONST_AGE_PARM);
|
||
|
||
seq = gen_sequence ();
|
||
end_sequence ();
|
||
emit_insn_after (seq, map->insns_at_start);
|
||
return temp;
|
||
}
|
||
else if (regno == VIRTUAL_INCOMING_ARGS_REGNUM)
|
||
{
|
||
/* Do the same for a block to contain any arguments referenced
|
||
in memory. */
|
||
rtx loc, seq;
|
||
int size = FUNCTION_ARGS_SIZE (DECL_SAVED_INSNS (map->fndecl));
|
||
|
||
start_sequence ();
|
||
loc = assign_stack_temp (BLKmode, size, 1);
|
||
loc = XEXP (loc, 0);
|
||
/* When arguments grow downward, the virtual incoming
|
||
args pointer points to the top of the argument block,
|
||
so the remapped location better do the same. */
|
||
#ifdef ARGS_GROW_DOWNWARD
|
||
loc = plus_constant (loc, size);
|
||
#endif
|
||
map->reg_map[regno] = temp
|
||
= force_reg (Pmode, force_operand (loc, NULL_RTX));
|
||
|
||
#ifdef STACK_BOUNDARY
|
||
mark_reg_pointer (map->reg_map[regno],
|
||
STACK_BOUNDARY / BITS_PER_UNIT);
|
||
#endif
|
||
|
||
SET_CONST_EQUIV_DATA (map, temp, loc, CONST_AGE_PARM);
|
||
|
||
seq = gen_sequence ();
|
||
end_sequence ();
|
||
emit_insn_after (seq, map->insns_at_start);
|
||
return temp;
|
||
}
|
||
else if (REG_FUNCTION_VALUE_P (orig))
|
||
{
|
||
/* This is a reference to the function return value. If
|
||
the function doesn't have a return value, error. If the
|
||
mode doesn't agree, and it ain't BLKmode, make a SUBREG. */
|
||
if (map->inline_target == 0)
|
||
/* Must be unrolling loops or replicating code if we
|
||
reach here, so return the register unchanged. */
|
||
return orig;
|
||
else if (GET_MODE (map->inline_target) != BLKmode
|
||
&& mode != GET_MODE (map->inline_target))
|
||
return gen_lowpart (mode, map->inline_target);
|
||
else
|
||
return map->inline_target;
|
||
}
|
||
return orig;
|
||
}
|
||
if (map->reg_map[regno] == NULL)
|
||
{
|
||
map->reg_map[regno] = gen_reg_rtx (mode);
|
||
REG_USERVAR_P (map->reg_map[regno]) = REG_USERVAR_P (orig);
|
||
REG_LOOP_TEST_P (map->reg_map[regno]) = REG_LOOP_TEST_P (orig);
|
||
RTX_UNCHANGING_P (map->reg_map[regno]) = RTX_UNCHANGING_P (orig);
|
||
/* A reg with REG_FUNCTION_VALUE_P true will never reach here. */
|
||
|
||
if (map->regno_pointer_flag[regno])
|
||
mark_reg_pointer (map->reg_map[regno],
|
||
map->regno_pointer_align[regno]);
|
||
}
|
||
return map->reg_map[regno];
|
||
|
||
case SUBREG:
|
||
copy = copy_rtx_and_substitute (SUBREG_REG (orig), map);
|
||
/* SUBREG is ordinary, but don't make nested SUBREGs. */
|
||
if (GET_CODE (copy) == SUBREG)
|
||
return gen_rtx_SUBREG (GET_MODE (orig), SUBREG_REG (copy),
|
||
SUBREG_WORD (orig) + SUBREG_WORD (copy));
|
||
else if (GET_CODE (copy) == CONCAT)
|
||
{
|
||
rtx retval = subreg_realpart_p (orig) ? XEXP (copy, 0) : XEXP (copy, 1);
|
||
|
||
if (GET_MODE (retval) == GET_MODE (orig))
|
||
return retval;
|
||
else
|
||
return gen_rtx_SUBREG (GET_MODE (orig), retval,
|
||
(SUBREG_WORD (orig) %
|
||
(GET_MODE_UNIT_SIZE (GET_MODE (SUBREG_REG (orig)))
|
||
/ (unsigned) UNITS_PER_WORD)));
|
||
}
|
||
else
|
||
return gen_rtx_SUBREG (GET_MODE (orig), copy,
|
||
SUBREG_WORD (orig));
|
||
|
||
case ADDRESSOF:
|
||
copy = gen_rtx_ADDRESSOF (mode,
|
||
copy_rtx_and_substitute (XEXP (orig, 0), map), 0);
|
||
SET_ADDRESSOF_DECL (copy, ADDRESSOF_DECL (orig));
|
||
regno = ADDRESSOF_REGNO (orig);
|
||
if (map->reg_map[regno])
|
||
regno = REGNO (map->reg_map[regno]);
|
||
else if (regno > LAST_VIRTUAL_REGISTER)
|
||
{
|
||
temp = XEXP (orig, 0);
|
||
map->reg_map[regno] = gen_reg_rtx (GET_MODE (temp));
|
||
REG_USERVAR_P (map->reg_map[regno]) = REG_USERVAR_P (temp);
|
||
REG_LOOP_TEST_P (map->reg_map[regno]) = REG_LOOP_TEST_P (temp);
|
||
RTX_UNCHANGING_P (map->reg_map[regno]) = RTX_UNCHANGING_P (temp);
|
||
/* A reg with REG_FUNCTION_VALUE_P true will never reach here. */
|
||
|
||
if (map->regno_pointer_flag[regno])
|
||
mark_reg_pointer (map->reg_map[regno],
|
||
map->regno_pointer_align[regno]);
|
||
regno = REGNO (map->reg_map[regno]);
|
||
}
|
||
ADDRESSOF_REGNO (copy) = regno;
|
||
return copy;
|
||
|
||
case USE:
|
||
case CLOBBER:
|
||
/* USE and CLOBBER are ordinary, but we convert (use (subreg foo))
|
||
to (use foo) if the original insn didn't have a subreg.
|
||
Removing the subreg distorts the VAX movstrhi pattern
|
||
by changing the mode of an operand. */
|
||
copy = copy_rtx_and_substitute (XEXP (orig, 0), map);
|
||
if (GET_CODE (copy) == SUBREG && GET_CODE (XEXP (orig, 0)) != SUBREG)
|
||
copy = SUBREG_REG (copy);
|
||
return gen_rtx_fmt_e (code, VOIDmode, copy);
|
||
|
||
case CODE_LABEL:
|
||
LABEL_PRESERVE_P (get_label_from_map (map, CODE_LABEL_NUMBER (orig)))
|
||
= LABEL_PRESERVE_P (orig);
|
||
return get_label_from_map (map, CODE_LABEL_NUMBER (orig));
|
||
|
||
case LABEL_REF:
|
||
copy = gen_rtx_LABEL_REF (mode,
|
||
LABEL_REF_NONLOCAL_P (orig) ? XEXP (orig, 0)
|
||
: get_label_from_map (map,
|
||
CODE_LABEL_NUMBER (XEXP (orig, 0))));
|
||
LABEL_OUTSIDE_LOOP_P (copy) = LABEL_OUTSIDE_LOOP_P (orig);
|
||
|
||
/* The fact that this label was previously nonlocal does not mean
|
||
it still is, so we must check if it is within the range of
|
||
this function's labels. */
|
||
LABEL_REF_NONLOCAL_P (copy)
|
||
= (LABEL_REF_NONLOCAL_P (orig)
|
||
&& ! (CODE_LABEL_NUMBER (XEXP (copy, 0)) >= get_first_label_num ()
|
||
&& CODE_LABEL_NUMBER (XEXP (copy, 0)) < max_label_num ()));
|
||
|
||
/* If we have made a nonlocal label local, it means that this
|
||
inlined call will be referring to our nonlocal goto handler.
|
||
So make sure we create one for this block; we normally would
|
||
not since this is not otherwise considered a "call". */
|
||
if (LABEL_REF_NONLOCAL_P (orig) && ! LABEL_REF_NONLOCAL_P (copy))
|
||
function_call_count++;
|
||
|
||
return copy;
|
||
|
||
case PC:
|
||
case CC0:
|
||
case CONST_INT:
|
||
return orig;
|
||
|
||
case SYMBOL_REF:
|
||
/* Symbols which represent the address of a label stored in the constant
|
||
pool must be modified to point to a constant pool entry for the
|
||
remapped label. Otherwise, symbols are returned unchanged. */
|
||
if (CONSTANT_POOL_ADDRESS_P (orig))
|
||
{
|
||
rtx constant = get_pool_constant (orig);
|
||
if (GET_CODE (constant) == LABEL_REF)
|
||
return XEXP (force_const_mem (GET_MODE (orig),
|
||
copy_rtx_and_substitute (constant,
|
||
map)),
|
||
0);
|
||
}
|
||
else
|
||
if (SYMBOL_REF_NEED_ADJUST (orig))
|
||
{
|
||
eif_eh_map = map;
|
||
return rethrow_symbol_map (orig,
|
||
expand_inline_function_eh_labelmap);
|
||
}
|
||
|
||
return orig;
|
||
|
||
case CONST_DOUBLE:
|
||
/* We have to make a new copy of this CONST_DOUBLE because don't want
|
||
to use the old value of CONST_DOUBLE_MEM. Also, this may be a
|
||
duplicate of a CONST_DOUBLE we have already seen. */
|
||
if (GET_MODE_CLASS (GET_MODE (orig)) == MODE_FLOAT)
|
||
{
|
||
REAL_VALUE_TYPE d;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (d, orig);
|
||
return CONST_DOUBLE_FROM_REAL_VALUE (d, GET_MODE (orig));
|
||
}
|
||
else
|
||
return immed_double_const (CONST_DOUBLE_LOW (orig),
|
||
CONST_DOUBLE_HIGH (orig), VOIDmode);
|
||
|
||
case CONST:
|
||
/* Make new constant pool entry for a constant
|
||
that was in the pool of the inline function. */
|
||
if (RTX_INTEGRATED_P (orig))
|
||
{
|
||
/* If this was an address of a constant pool entry that itself
|
||
had to be placed in the constant pool, it might not be a
|
||
valid address. So the recursive call below might turn it
|
||
into a register. In that case, it isn't a constant any
|
||
more, so return it. This has the potential of changing a
|
||
MEM into a REG, but we'll assume that it safe. */
|
||
temp = copy_rtx_and_substitute (XEXP (orig, 0), map);
|
||
if (! CONSTANT_P (temp))
|
||
return temp;
|
||
return validize_mem (force_const_mem (GET_MODE (orig), temp));
|
||
}
|
||
break;
|
||
|
||
case ADDRESS:
|
||
/* If from constant pool address, make new constant pool entry and
|
||
return its address. */
|
||
if (! RTX_INTEGRATED_P (orig))
|
||
abort ();
|
||
|
||
temp
|
||
= force_const_mem (GET_MODE (XEXP (orig, 0)),
|
||
copy_rtx_and_substitute (XEXP (XEXP (orig, 0), 0),
|
||
map));
|
||
|
||
#if 0
|
||
/* Legitimizing the address here is incorrect.
|
||
|
||
The only ADDRESS rtx's that can reach here are ones created by
|
||
save_constants. Hence the operand of the ADDRESS is always valid
|
||
in this position of the instruction, since the original rtx without
|
||
the ADDRESS was valid.
|
||
|
||
The reason we don't legitimize the address here is that on the
|
||
Sparc, the caller may have a (high ...) surrounding this ADDRESS.
|
||
This code forces the operand of the address to a register, which
|
||
fails because we can not take the HIGH part of a register.
|
||
|
||
Also, change_address may create new registers. These registers
|
||
will not have valid reg_map entries. This can cause try_constants()
|
||
to fail because assumes that all registers in the rtx have valid
|
||
reg_map entries, and it may end up replacing one of these new
|
||
registers with junk. */
|
||
|
||
if (! memory_address_p (GET_MODE (temp), XEXP (temp, 0)))
|
||
temp = change_address (temp, GET_MODE (temp), XEXP (temp, 0));
|
||
#endif
|
||
|
||
temp = XEXP (temp, 0);
|
||
|
||
#ifdef POINTERS_EXTEND_UNSIGNED
|
||
if (GET_MODE (temp) != GET_MODE (orig))
|
||
temp = convert_memory_address (GET_MODE (orig), temp);
|
||
#endif
|
||
|
||
return temp;
|
||
|
||
case ASM_OPERANDS:
|
||
/* If a single asm insn contains multiple output operands
|
||
then it contains multiple ASM_OPERANDS rtx's that share operand 3.
|
||
We must make sure that the copied insn continues to share it. */
|
||
if (map->orig_asm_operands_vector == XVEC (orig, 3))
|
||
{
|
||
copy = rtx_alloc (ASM_OPERANDS);
|
||
copy->volatil = orig->volatil;
|
||
XSTR (copy, 0) = XSTR (orig, 0);
|
||
XSTR (copy, 1) = XSTR (orig, 1);
|
||
XINT (copy, 2) = XINT (orig, 2);
|
||
XVEC (copy, 3) = map->copy_asm_operands_vector;
|
||
XVEC (copy, 4) = map->copy_asm_constraints_vector;
|
||
XSTR (copy, 5) = XSTR (orig, 5);
|
||
XINT (copy, 6) = XINT (orig, 6);
|
||
return copy;
|
||
}
|
||
break;
|
||
|
||
case CALL:
|
||
/* This is given special treatment because the first
|
||
operand of a CALL is a (MEM ...) which may get
|
||
forced into a register for cse. This is undesirable
|
||
if function-address cse isn't wanted or if we won't do cse. */
|
||
#ifndef NO_FUNCTION_CSE
|
||
if (! (optimize && ! flag_no_function_cse))
|
||
#endif
|
||
return gen_rtx_CALL (GET_MODE (orig),
|
||
gen_rtx_MEM (GET_MODE (XEXP (orig, 0)),
|
||
copy_rtx_and_substitute (XEXP (XEXP (orig, 0), 0), map)),
|
||
copy_rtx_and_substitute (XEXP (orig, 1), map));
|
||
break;
|
||
|
||
#if 0
|
||
/* Must be ifdefed out for loop unrolling to work. */
|
||
case RETURN:
|
||
abort ();
|
||
#endif
|
||
|
||
case SET:
|
||
/* If this is setting fp or ap, it means that we have a nonlocal goto.
|
||
Adjust the setting by the offset of the area we made.
|
||
If the nonlocal goto is into the current function,
|
||
this will result in unnecessarily bad code, but should work. */
|
||
if (SET_DEST (orig) == virtual_stack_vars_rtx
|
||
|| SET_DEST (orig) == virtual_incoming_args_rtx)
|
||
{
|
||
/* In case a translation hasn't occurred already, make one now. */
|
||
rtx equiv_reg;
|
||
rtx equiv_loc;
|
||
HOST_WIDE_INT loc_offset;
|
||
|
||
copy_rtx_and_substitute (SET_DEST (orig), map);
|
||
equiv_reg = map->reg_map[REGNO (SET_DEST (orig))];
|
||
equiv_loc = VARRAY_CONST_EQUIV (map->const_equiv_varray, REGNO (equiv_reg)).rtx;
|
||
loc_offset
|
||
= GET_CODE (equiv_loc) == REG ? 0 : INTVAL (XEXP (equiv_loc, 1));
|
||
return gen_rtx_SET (VOIDmode, SET_DEST (orig),
|
||
force_operand
|
||
(plus_constant
|
||
(copy_rtx_and_substitute (SET_SRC (orig), map),
|
||
- loc_offset),
|
||
NULL_RTX));
|
||
}
|
||
break;
|
||
|
||
case MEM:
|
||
copy = rtx_alloc (MEM);
|
||
PUT_MODE (copy, mode);
|
||
XEXP (copy, 0) = copy_rtx_and_substitute (XEXP (orig, 0), map);
|
||
MEM_COPY_ATTRIBUTES (copy, orig);
|
||
MEM_ALIAS_SET (copy) = MEM_ALIAS_SET (orig);
|
||
|
||
/* If doing function inlining, this MEM might not be const in the
|
||
function that it is being inlined into, and thus may not be
|
||
unchanging after function inlining. Constant pool references are
|
||
handled elsewhere, so this doesn't lose RTX_UNCHANGING_P bits
|
||
for them. */
|
||
if (! map->integrating)
|
||
RTX_UNCHANGING_P (copy) = RTX_UNCHANGING_P (orig);
|
||
|
||
return copy;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
copy = rtx_alloc (code);
|
||
PUT_MODE (copy, mode);
|
||
copy->in_struct = orig->in_struct;
|
||
copy->volatil = orig->volatil;
|
||
copy->unchanging = orig->unchanging;
|
||
|
||
format_ptr = GET_RTX_FORMAT (GET_CODE (copy));
|
||
|
||
for (i = 0; i < GET_RTX_LENGTH (GET_CODE (copy)); i++)
|
||
{
|
||
switch (*format_ptr++)
|
||
{
|
||
case '0':
|
||
XEXP (copy, i) = XEXP (orig, i);
|
||
break;
|
||
|
||
case 'e':
|
||
XEXP (copy, i) = copy_rtx_and_substitute (XEXP (orig, i), map);
|
||
break;
|
||
|
||
case 'u':
|
||
/* Change any references to old-insns to point to the
|
||
corresponding copied insns. */
|
||
XEXP (copy, i) = map->insn_map[INSN_UID (XEXP (orig, i))];
|
||
break;
|
||
|
||
case 'E':
|
||
XVEC (copy, i) = XVEC (orig, i);
|
||
if (XVEC (orig, i) != NULL && XVECLEN (orig, i) != 0)
|
||
{
|
||
XVEC (copy, i) = rtvec_alloc (XVECLEN (orig, i));
|
||
for (j = 0; j < XVECLEN (copy, i); j++)
|
||
XVECEXP (copy, i, j)
|
||
= copy_rtx_and_substitute (XVECEXP (orig, i, j), map);
|
||
}
|
||
break;
|
||
|
||
case 'w':
|
||
XWINT (copy, i) = XWINT (orig, i);
|
||
break;
|
||
|
||
case 'i':
|
||
XINT (copy, i) = XINT (orig, i);
|
||
break;
|
||
|
||
case 's':
|
||
XSTR (copy, i) = XSTR (orig, i);
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
if (code == ASM_OPERANDS && map->orig_asm_operands_vector == 0)
|
||
{
|
||
map->orig_asm_operands_vector = XVEC (orig, 3);
|
||
map->copy_asm_operands_vector = XVEC (copy, 3);
|
||
map->copy_asm_constraints_vector = XVEC (copy, 4);
|
||
}
|
||
|
||
return copy;
|
||
}
|
||
|
||
/* Substitute known constant values into INSN, if that is valid. */
|
||
|
||
void
|
||
try_constants (insn, map)
|
||
rtx insn;
|
||
struct inline_remap *map;
|
||
{
|
||
int i;
|
||
|
||
map->num_sets = 0;
|
||
subst_constants (&PATTERN (insn), insn, map);
|
||
|
||
/* Apply the changes if they are valid; otherwise discard them. */
|
||
apply_change_group ();
|
||
|
||
/* Show we don't know the value of anything stored or clobbered. */
|
||
note_stores (PATTERN (insn), mark_stores);
|
||
map->last_pc_value = 0;
|
||
#ifdef HAVE_cc0
|
||
map->last_cc0_value = 0;
|
||
#endif
|
||
|
||
/* Set up any constant equivalences made in this insn. */
|
||
for (i = 0; i < map->num_sets; i++)
|
||
{
|
||
if (GET_CODE (map->equiv_sets[i].dest) == REG)
|
||
{
|
||
int regno = REGNO (map->equiv_sets[i].dest);
|
||
|
||
MAYBE_EXTEND_CONST_EQUIV_VARRAY (map, regno);
|
||
if (VARRAY_CONST_EQUIV (map->const_equiv_varray, regno).rtx == 0
|
||
/* Following clause is a hack to make case work where GNU C++
|
||
reassigns a variable to make cse work right. */
|
||
|| ! rtx_equal_p (VARRAY_CONST_EQUIV (map->const_equiv_varray,
|
||
regno).rtx,
|
||
map->equiv_sets[i].equiv))
|
||
SET_CONST_EQUIV_DATA (map, map->equiv_sets[i].dest,
|
||
map->equiv_sets[i].equiv, map->const_age);
|
||
}
|
||
else if (map->equiv_sets[i].dest == pc_rtx)
|
||
map->last_pc_value = map->equiv_sets[i].equiv;
|
||
#ifdef HAVE_cc0
|
||
else if (map->equiv_sets[i].dest == cc0_rtx)
|
||
map->last_cc0_value = map->equiv_sets[i].equiv;
|
||
#endif
|
||
}
|
||
}
|
||
|
||
/* Substitute known constants for pseudo regs in the contents of LOC,
|
||
which are part of INSN.
|
||
If INSN is zero, the substitution should always be done (this is used to
|
||
update DECL_RTL).
|
||
These changes are taken out by try_constants if the result is not valid.
|
||
|
||
Note that we are more concerned with determining when the result of a SET
|
||
is a constant, for further propagation, than actually inserting constants
|
||
into insns; cse will do the latter task better.
|
||
|
||
This function is also used to adjust address of items previously addressed
|
||
via the virtual stack variable or virtual incoming arguments registers. */
|
||
|
||
static void
|
||
subst_constants (loc, insn, map)
|
||
rtx *loc;
|
||
rtx insn;
|
||
struct inline_remap *map;
|
||
{
|
||
rtx x = *loc;
|
||
register int i;
|
||
register enum rtx_code code;
|
||
register char *format_ptr;
|
||
int num_changes = num_validated_changes ();
|
||
rtx new = 0;
|
||
enum machine_mode op0_mode = MAX_MACHINE_MODE;
|
||
|
||
code = GET_CODE (x);
|
||
|
||
switch (code)
|
||
{
|
||
case PC:
|
||
case CONST_INT:
|
||
case CONST_DOUBLE:
|
||
case SYMBOL_REF:
|
||
case CONST:
|
||
case LABEL_REF:
|
||
case ADDRESS:
|
||
return;
|
||
|
||
#ifdef HAVE_cc0
|
||
case CC0:
|
||
validate_change (insn, loc, map->last_cc0_value, 1);
|
||
return;
|
||
#endif
|
||
|
||
case USE:
|
||
case CLOBBER:
|
||
/* The only thing we can do with a USE or CLOBBER is possibly do
|
||
some substitutions in a MEM within it. */
|
||
if (GET_CODE (XEXP (x, 0)) == MEM)
|
||
subst_constants (&XEXP (XEXP (x, 0), 0), insn, map);
|
||
return;
|
||
|
||
case REG:
|
||
/* Substitute for parms and known constants. Don't replace
|
||
hard regs used as user variables with constants. */
|
||
{
|
||
int regno = REGNO (x);
|
||
struct const_equiv_data *p;
|
||
|
||
if (! (regno < FIRST_PSEUDO_REGISTER && REG_USERVAR_P (x))
|
||
&& regno < VARRAY_SIZE (map->const_equiv_varray)
|
||
&& (p = &VARRAY_CONST_EQUIV (map->const_equiv_varray, regno),
|
||
p->rtx != 0)
|
||
&& p->age >= map->const_age)
|
||
validate_change (insn, loc, p->rtx, 1);
|
||
return;
|
||
}
|
||
|
||
case SUBREG:
|
||
/* SUBREG applied to something other than a reg
|
||
should be treated as ordinary, since that must
|
||
be a special hack and we don't know how to treat it specially.
|
||
Consider for example mulsidi3 in m68k.md.
|
||
Ordinary SUBREG of a REG needs this special treatment. */
|
||
if (GET_CODE (SUBREG_REG (x)) == REG)
|
||
{
|
||
rtx inner = SUBREG_REG (x);
|
||
rtx new = 0;
|
||
|
||
/* We can't call subst_constants on &SUBREG_REG (x) because any
|
||
constant or SUBREG wouldn't be valid inside our SUBEG. Instead,
|
||
see what is inside, try to form the new SUBREG and see if that is
|
||
valid. We handle two cases: extracting a full word in an
|
||
integral mode and extracting the low part. */
|
||
subst_constants (&inner, NULL_RTX, map);
|
||
|
||
if (GET_MODE_CLASS (GET_MODE (x)) == MODE_INT
|
||
&& GET_MODE_SIZE (GET_MODE (x)) == UNITS_PER_WORD
|
||
&& GET_MODE (SUBREG_REG (x)) != VOIDmode)
|
||
new = operand_subword (inner, SUBREG_WORD (x), 0,
|
||
GET_MODE (SUBREG_REG (x)));
|
||
|
||
cancel_changes (num_changes);
|
||
if (new == 0 && subreg_lowpart_p (x))
|
||
new = gen_lowpart_common (GET_MODE (x), inner);
|
||
|
||
if (new)
|
||
validate_change (insn, loc, new, 1);
|
||
|
||
return;
|
||
}
|
||
break;
|
||
|
||
case MEM:
|
||
subst_constants (&XEXP (x, 0), insn, map);
|
||
|
||
/* If a memory address got spoiled, change it back. */
|
||
if (insn != 0 && num_validated_changes () != num_changes
|
||
&& !memory_address_p (GET_MODE (x), XEXP (x, 0)))
|
||
cancel_changes (num_changes);
|
||
return;
|
||
|
||
case SET:
|
||
{
|
||
/* Substitute constants in our source, and in any arguments to a
|
||
complex (e..g, ZERO_EXTRACT) destination, but not in the destination
|
||
itself. */
|
||
rtx *dest_loc = &SET_DEST (x);
|
||
rtx dest = *dest_loc;
|
||
rtx src, tem;
|
||
|
||
subst_constants (&SET_SRC (x), insn, map);
|
||
src = SET_SRC (x);
|
||
|
||
while (GET_CODE (*dest_loc) == ZERO_EXTRACT
|
||
|| GET_CODE (*dest_loc) == SUBREG
|
||
|| GET_CODE (*dest_loc) == STRICT_LOW_PART)
|
||
{
|
||
if (GET_CODE (*dest_loc) == ZERO_EXTRACT)
|
||
{
|
||
subst_constants (&XEXP (*dest_loc, 1), insn, map);
|
||
subst_constants (&XEXP (*dest_loc, 2), insn, map);
|
||
}
|
||
dest_loc = &XEXP (*dest_loc, 0);
|
||
}
|
||
|
||
/* Do substitute in the address of a destination in memory. */
|
||
if (GET_CODE (*dest_loc) == MEM)
|
||
subst_constants (&XEXP (*dest_loc, 0), insn, map);
|
||
|
||
/* Check for the case of DEST a SUBREG, both it and the underlying
|
||
register are less than one word, and the SUBREG has the wider mode.
|
||
In the case, we are really setting the underlying register to the
|
||
source converted to the mode of DEST. So indicate that. */
|
||
if (GET_CODE (dest) == SUBREG
|
||
&& GET_MODE_SIZE (GET_MODE (dest)) <= UNITS_PER_WORD
|
||
&& GET_MODE_SIZE (GET_MODE (SUBREG_REG (dest))) <= UNITS_PER_WORD
|
||
&& (GET_MODE_SIZE (GET_MODE (SUBREG_REG (dest)))
|
||
<= GET_MODE_SIZE (GET_MODE (dest)))
|
||
&& (tem = gen_lowpart_if_possible (GET_MODE (SUBREG_REG (dest)),
|
||
src)))
|
||
src = tem, dest = SUBREG_REG (dest);
|
||
|
||
/* If storing a recognizable value save it for later recording. */
|
||
if ((map->num_sets < MAX_RECOG_OPERANDS)
|
||
&& (CONSTANT_P (src)
|
||
|| (GET_CODE (src) == REG
|
||
&& (REGNO (src) == VIRTUAL_INCOMING_ARGS_REGNUM
|
||
|| REGNO (src) == VIRTUAL_STACK_VARS_REGNUM))
|
||
|| (GET_CODE (src) == PLUS
|
||
&& GET_CODE (XEXP (src, 0)) == REG
|
||
&& (REGNO (XEXP (src, 0)) == VIRTUAL_INCOMING_ARGS_REGNUM
|
||
|| REGNO (XEXP (src, 0)) == VIRTUAL_STACK_VARS_REGNUM)
|
||
&& CONSTANT_P (XEXP (src, 1)))
|
||
|| GET_CODE (src) == COMPARE
|
||
#ifdef HAVE_cc0
|
||
|| dest == cc0_rtx
|
||
#endif
|
||
|| (dest == pc_rtx
|
||
&& (src == pc_rtx || GET_CODE (src) == RETURN
|
||
|| GET_CODE (src) == LABEL_REF))))
|
||
{
|
||
/* Normally, this copy won't do anything. But, if SRC is a COMPARE
|
||
it will cause us to save the COMPARE with any constants
|
||
substituted, which is what we want for later. */
|
||
map->equiv_sets[map->num_sets].equiv = copy_rtx (src);
|
||
map->equiv_sets[map->num_sets++].dest = dest;
|
||
}
|
||
}
|
||
return;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
format_ptr = GET_RTX_FORMAT (code);
|
||
|
||
/* If the first operand is an expression, save its mode for later. */
|
||
if (*format_ptr == 'e')
|
||
op0_mode = GET_MODE (XEXP (x, 0));
|
||
|
||
for (i = 0; i < GET_RTX_LENGTH (code); i++)
|
||
{
|
||
switch (*format_ptr++)
|
||
{
|
||
case '0':
|
||
break;
|
||
|
||
case 'e':
|
||
if (XEXP (x, i))
|
||
subst_constants (&XEXP (x, i), insn, map);
|
||
break;
|
||
|
||
case 'u':
|
||
case 'i':
|
||
case 's':
|
||
case 'w':
|
||
break;
|
||
|
||
case 'E':
|
||
if (XVEC (x, i) != NULL && XVECLEN (x, i) != 0)
|
||
{
|
||
int j;
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
subst_constants (&XVECEXP (x, i, j), insn, map);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
}
|
||
|
||
/* If this is a commutative operation, move a constant to the second
|
||
operand unless the second operand is already a CONST_INT. */
|
||
if ((GET_RTX_CLASS (code) == 'c' || code == NE || code == EQ)
|
||
&& CONSTANT_P (XEXP (x, 0)) && GET_CODE (XEXP (x, 1)) != CONST_INT)
|
||
{
|
||
rtx tem = XEXP (x, 0);
|
||
validate_change (insn, &XEXP (x, 0), XEXP (x, 1), 1);
|
||
validate_change (insn, &XEXP (x, 1), tem, 1);
|
||
}
|
||
|
||
/* Simplify the expression in case we put in some constants. */
|
||
switch (GET_RTX_CLASS (code))
|
||
{
|
||
case '1':
|
||
if (op0_mode == MAX_MACHINE_MODE)
|
||
abort ();
|
||
new = simplify_unary_operation (code, GET_MODE (x),
|
||
XEXP (x, 0), op0_mode);
|
||
break;
|
||
|
||
case '<':
|
||
{
|
||
enum machine_mode op_mode = GET_MODE (XEXP (x, 0));
|
||
if (op_mode == VOIDmode)
|
||
op_mode = GET_MODE (XEXP (x, 1));
|
||
new = simplify_relational_operation (code, op_mode,
|
||
XEXP (x, 0), XEXP (x, 1));
|
||
#ifdef FLOAT_STORE_FLAG_VALUE
|
||
if (new != 0 && GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT)
|
||
new = ((new == const0_rtx) ? CONST0_RTX (GET_MODE (x))
|
||
: CONST_DOUBLE_FROM_REAL_VALUE (FLOAT_STORE_FLAG_VALUE,
|
||
GET_MODE (x)));
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
case '2':
|
||
case 'c':
|
||
new = simplify_binary_operation (code, GET_MODE (x),
|
||
XEXP (x, 0), XEXP (x, 1));
|
||
break;
|
||
|
||
case 'b':
|
||
case '3':
|
||
if (op0_mode == MAX_MACHINE_MODE)
|
||
abort ();
|
||
new = simplify_ternary_operation (code, GET_MODE (x), op0_mode,
|
||
XEXP (x, 0), XEXP (x, 1), XEXP (x, 2));
|
||
break;
|
||
}
|
||
|
||
if (new)
|
||
validate_change (insn, loc, new, 1);
|
||
}
|
||
|
||
/* Show that register modified no longer contain known constants. We are
|
||
called from note_stores with parts of the new insn. */
|
||
|
||
void
|
||
mark_stores (dest, x)
|
||
rtx dest;
|
||
rtx x ATTRIBUTE_UNUSED;
|
||
{
|
||
int regno = -1;
|
||
enum machine_mode mode;
|
||
|
||
/* DEST is always the innermost thing set, except in the case of
|
||
SUBREGs of hard registers. */
|
||
|
||
if (GET_CODE (dest) == REG)
|
||
regno = REGNO (dest), mode = GET_MODE (dest);
|
||
else if (GET_CODE (dest) == SUBREG && GET_CODE (SUBREG_REG (dest)) == REG)
|
||
{
|
||
regno = REGNO (SUBREG_REG (dest)) + SUBREG_WORD (dest);
|
||
mode = GET_MODE (SUBREG_REG (dest));
|
||
}
|
||
|
||
if (regno >= 0)
|
||
{
|
||
int last_reg = (regno >= FIRST_PSEUDO_REGISTER ? regno
|
||
: regno + HARD_REGNO_NREGS (regno, mode) - 1);
|
||
int i;
|
||
|
||
/* Ignore virtual stack var or virtual arg register since those
|
||
are handled separately. */
|
||
if (regno != VIRTUAL_INCOMING_ARGS_REGNUM
|
||
&& regno != VIRTUAL_STACK_VARS_REGNUM)
|
||
for (i = regno; i <= last_reg; i++)
|
||
if (i < VARRAY_SIZE (global_const_equiv_varray))
|
||
VARRAY_CONST_EQUIV (global_const_equiv_varray, i).rtx = 0;
|
||
}
|
||
}
|
||
|
||
/* If any CONST expressions with RTX_INTEGRATED_P are present in the rtx
|
||
pointed to by PX, they represent constants in the constant pool.
|
||
Replace these with a new memory reference obtained from force_const_mem.
|
||
Similarly, ADDRESS expressions with RTX_INTEGRATED_P represent the
|
||
address of a constant pool entry. Replace them with the address of
|
||
a new constant pool entry obtained from force_const_mem. */
|
||
|
||
static void
|
||
restore_constants (px)
|
||
rtx *px;
|
||
{
|
||
rtx x = *px;
|
||
int i, j;
|
||
char *fmt;
|
||
|
||
if (x == 0)
|
||
return;
|
||
|
||
if (GET_CODE (x) == CONST_DOUBLE)
|
||
{
|
||
/* We have to make a new CONST_DOUBLE to ensure that we account for
|
||
it correctly. Using the old CONST_DOUBLE_MEM data is wrong. */
|
||
if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT)
|
||
{
|
||
REAL_VALUE_TYPE d;
|
||
|
||
REAL_VALUE_FROM_CONST_DOUBLE (d, x);
|
||
*px = CONST_DOUBLE_FROM_REAL_VALUE (d, GET_MODE (x));
|
||
}
|
||
else
|
||
*px = immed_double_const (CONST_DOUBLE_LOW (x), CONST_DOUBLE_HIGH (x),
|
||
VOIDmode);
|
||
}
|
||
|
||
else if (RTX_INTEGRATED_P (x) && GET_CODE (x) == CONST)
|
||
{
|
||
restore_constants (&XEXP (x, 0));
|
||
*px = validize_mem (force_const_mem (GET_MODE (x), XEXP (x, 0)));
|
||
}
|
||
else if (RTX_INTEGRATED_P (x) && GET_CODE (x) == SUBREG)
|
||
{
|
||
/* This must be (subreg/i:M1 (const/i:M2 ...) 0). */
|
||
rtx new = XEXP (SUBREG_REG (x), 0);
|
||
|
||
restore_constants (&new);
|
||
new = force_const_mem (GET_MODE (SUBREG_REG (x)), new);
|
||
PUT_MODE (new, GET_MODE (x));
|
||
*px = validize_mem (new);
|
||
}
|
||
else if (RTX_INTEGRATED_P (x) && GET_CODE (x) == ADDRESS)
|
||
{
|
||
rtx new = XEXP (force_const_mem (GET_MODE (XEXP (x, 0)),
|
||
XEXP (XEXP (x, 0), 0)),
|
||
0);
|
||
|
||
#ifdef POINTERS_EXTEND_UNSIGNED
|
||
if (GET_MODE (new) != GET_MODE (x))
|
||
new = convert_memory_address (GET_MODE (x), new);
|
||
#endif
|
||
|
||
*px = new;
|
||
}
|
||
else
|
||
{
|
||
fmt = GET_RTX_FORMAT (GET_CODE (x));
|
||
for (i = 0; i < GET_RTX_LENGTH (GET_CODE (x)); i++)
|
||
{
|
||
switch (*fmt++)
|
||
{
|
||
case 'E':
|
||
for (j = 0; j < XVECLEN (x, i); j++)
|
||
restore_constants (&XVECEXP (x, i, j));
|
||
break;
|
||
|
||
case 'e':
|
||
restore_constants (&XEXP (x, i));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Given a pointer to some BLOCK node, if the BLOCK_ABSTRACT_ORIGIN for the
|
||
given BLOCK node is NULL, set the BLOCK_ABSTRACT_ORIGIN for the node so
|
||
that it points to the node itself, thus indicating that the node is its
|
||
own (abstract) origin. Additionally, if the BLOCK_ABSTRACT_ORIGIN for
|
||
the given node is NULL, recursively descend the decl/block tree which
|
||
it is the root of, and for each other ..._DECL or BLOCK node contained
|
||
therein whose DECL_ABSTRACT_ORIGINs or BLOCK_ABSTRACT_ORIGINs are also
|
||
still NULL, set *their* DECL_ABSTRACT_ORIGIN or BLOCK_ABSTRACT_ORIGIN
|
||
values to point to themselves. */
|
||
|
||
static void
|
||
set_block_origin_self (stmt)
|
||
register tree stmt;
|
||
{
|
||
if (BLOCK_ABSTRACT_ORIGIN (stmt) == NULL_TREE)
|
||
{
|
||
BLOCK_ABSTRACT_ORIGIN (stmt) = stmt;
|
||
|
||
{
|
||
register tree local_decl;
|
||
|
||
for (local_decl = BLOCK_VARS (stmt);
|
||
local_decl != NULL_TREE;
|
||
local_decl = TREE_CHAIN (local_decl))
|
||
set_decl_origin_self (local_decl); /* Potential recursion. */
|
||
}
|
||
|
||
{
|
||
register tree subblock;
|
||
|
||
for (subblock = BLOCK_SUBBLOCKS (stmt);
|
||
subblock != NULL_TREE;
|
||
subblock = BLOCK_CHAIN (subblock))
|
||
set_block_origin_self (subblock); /* Recurse. */
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Given a pointer to some ..._DECL node, if the DECL_ABSTRACT_ORIGIN for
|
||
the given ..._DECL node is NULL, set the DECL_ABSTRACT_ORIGIN for the
|
||
node to so that it points to the node itself, thus indicating that the
|
||
node represents its own (abstract) origin. Additionally, if the
|
||
DECL_ABSTRACT_ORIGIN for the given node is NULL, recursively descend
|
||
the decl/block tree of which the given node is the root of, and for
|
||
each other ..._DECL or BLOCK node contained therein whose
|
||
DECL_ABSTRACT_ORIGINs or BLOCK_ABSTRACT_ORIGINs are also still NULL,
|
||
set *their* DECL_ABSTRACT_ORIGIN or BLOCK_ABSTRACT_ORIGIN values to
|
||
point to themselves. */
|
||
|
||
static void
|
||
set_decl_origin_self (decl)
|
||
register tree decl;
|
||
{
|
||
if (DECL_ABSTRACT_ORIGIN (decl) == NULL_TREE)
|
||
{
|
||
DECL_ABSTRACT_ORIGIN (decl) = decl;
|
||
if (TREE_CODE (decl) == FUNCTION_DECL)
|
||
{
|
||
register tree arg;
|
||
|
||
for (arg = DECL_ARGUMENTS (decl); arg; arg = TREE_CHAIN (arg))
|
||
DECL_ABSTRACT_ORIGIN (arg) = arg;
|
||
if (DECL_INITIAL (decl) != NULL_TREE
|
||
&& DECL_INITIAL (decl) != error_mark_node)
|
||
set_block_origin_self (DECL_INITIAL (decl));
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Given a pointer to some BLOCK node, and a boolean value to set the
|
||
"abstract" flags to, set that value into the BLOCK_ABSTRACT flag for
|
||
the given block, and for all local decls and all local sub-blocks
|
||
(recursively) which are contained therein. */
|
||
|
||
static void
|
||
set_block_abstract_flags (stmt, setting)
|
||
register tree stmt;
|
||
register int setting;
|
||
{
|
||
register tree local_decl;
|
||
register tree subblock;
|
||
|
||
BLOCK_ABSTRACT (stmt) = setting;
|
||
|
||
for (local_decl = BLOCK_VARS (stmt);
|
||
local_decl != NULL_TREE;
|
||
local_decl = TREE_CHAIN (local_decl))
|
||
set_decl_abstract_flags (local_decl, setting);
|
||
|
||
for (subblock = BLOCK_SUBBLOCKS (stmt);
|
||
subblock != NULL_TREE;
|
||
subblock = BLOCK_CHAIN (subblock))
|
||
set_block_abstract_flags (subblock, setting);
|
||
}
|
||
|
||
/* Given a pointer to some ..._DECL node, and a boolean value to set the
|
||
"abstract" flags to, set that value into the DECL_ABSTRACT flag for the
|
||
given decl, and (in the case where the decl is a FUNCTION_DECL) also
|
||
set the abstract flags for all of the parameters, local vars, local
|
||
blocks and sub-blocks (recursively) to the same setting. */
|
||
|
||
void
|
||
set_decl_abstract_flags (decl, setting)
|
||
register tree decl;
|
||
register int setting;
|
||
{
|
||
DECL_ABSTRACT (decl) = setting;
|
||
if (TREE_CODE (decl) == FUNCTION_DECL)
|
||
{
|
||
register tree arg;
|
||
|
||
for (arg = DECL_ARGUMENTS (decl); arg; arg = TREE_CHAIN (arg))
|
||
DECL_ABSTRACT (arg) = setting;
|
||
if (DECL_INITIAL (decl) != NULL_TREE
|
||
&& DECL_INITIAL (decl) != error_mark_node)
|
||
set_block_abstract_flags (DECL_INITIAL (decl), setting);
|
||
}
|
||
}
|
||
|
||
/* Output the assembly language code for the function FNDECL
|
||
from its DECL_SAVED_INSNS. Used for inline functions that are output
|
||
at end of compilation instead of where they came in the source. */
|
||
|
||
void
|
||
output_inline_function (fndecl)
|
||
tree fndecl;
|
||
{
|
||
rtx head;
|
||
rtx last;
|
||
|
||
/* Things we allocate from here on are part of this function, not
|
||
permanent. */
|
||
temporary_allocation ();
|
||
|
||
head = DECL_SAVED_INSNS (fndecl);
|
||
current_function_decl = fndecl;
|
||
|
||
/* This call is only used to initialize global variables. */
|
||
init_function_start (fndecl, "lossage", 1);
|
||
|
||
/* Redo parameter determinations in case the FUNCTION_...
|
||
macros took machine-specific actions that need to be redone. */
|
||
assign_parms (fndecl, 1);
|
||
|
||
/* Set stack frame size. */
|
||
assign_stack_local (BLKmode, DECL_FRAME_SIZE (fndecl), 0);
|
||
|
||
/* The first is a bit of a lie (the array may be larger), but doesn't
|
||
matter too much and it isn't worth saving the actual bound. */
|
||
reg_rtx_no = regno_pointer_flag_length = MAX_REGNUM (head);
|
||
regno_reg_rtx = (rtx *) INLINE_REGNO_REG_RTX (head);
|
||
regno_pointer_flag = INLINE_REGNO_POINTER_FLAG (head);
|
||
regno_pointer_align = INLINE_REGNO_POINTER_ALIGN (head);
|
||
max_parm_reg = MAX_PARMREG (head);
|
||
parm_reg_stack_loc = (rtx *) PARMREG_STACK_LOC (head);
|
||
|
||
stack_slot_list = STACK_SLOT_LIST (head);
|
||
forced_labels = FORCED_LABELS (head);
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_HAS_COMPUTED_JUMP)
|
||
current_function_has_computed_jump = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_CALLS_ALLOCA)
|
||
current_function_calls_alloca = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_CALLS_SETJMP)
|
||
current_function_calls_setjmp = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_CALLS_LONGJMP)
|
||
current_function_calls_longjmp = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_RETURNS_STRUCT)
|
||
current_function_returns_struct = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_RETURNS_PCC_STRUCT)
|
||
current_function_returns_pcc_struct = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_NEEDS_CONTEXT)
|
||
current_function_needs_context = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_HAS_NONLOCAL_LABEL)
|
||
current_function_has_nonlocal_label = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_RETURNS_POINTER)
|
||
current_function_returns_pointer = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_USES_CONST_POOL)
|
||
current_function_uses_const_pool = 1;
|
||
|
||
if (FUNCTION_FLAGS (head) & FUNCTION_FLAGS_USES_PIC_OFFSET_TABLE)
|
||
current_function_uses_pic_offset_table = 1;
|
||
|
||
current_function_outgoing_args_size = OUTGOING_ARGS_SIZE (head);
|
||
current_function_pops_args = POPS_ARGS (head);
|
||
|
||
/* This is the only thing the expand_function_end call that uses to be here
|
||
actually does and that call can cause problems. */
|
||
immediate_size_expand--;
|
||
|
||
/* Find last insn and rebuild the constant pool. */
|
||
for (last = FIRST_PARM_INSN (head);
|
||
NEXT_INSN (last); last = NEXT_INSN (last))
|
||
{
|
||
if (GET_RTX_CLASS (GET_CODE (last)) == 'i')
|
||
{
|
||
restore_constants (&PATTERN (last));
|
||
restore_constants (®_NOTES (last));
|
||
}
|
||
}
|
||
|
||
set_new_first_and_last_insn (FIRST_PARM_INSN (head), last);
|
||
set_new_first_and_last_label_num (FIRST_LABELNO (head), LAST_LABELNO (head));
|
||
|
||
/* We must have already output DWARF debugging information for the
|
||
original (abstract) inline function declaration/definition, so
|
||
we want to make sure that the debugging information we generate
|
||
for this special instance of the inline function refers back to
|
||
the information we already generated. To make sure that happens,
|
||
we simply have to set the DECL_ABSTRACT_ORIGIN for the function
|
||
node (and for all of the local ..._DECL nodes which are its children)
|
||
so that they all point to themselves. */
|
||
|
||
set_decl_origin_self (fndecl);
|
||
|
||
/* We're not deferring this any longer. */
|
||
DECL_DEFER_OUTPUT (fndecl) = 0;
|
||
|
||
/* We can't inline this anymore. */
|
||
DECL_INLINE (fndecl) = 0;
|
||
|
||
/* Compile this function all the way down to assembly code. */
|
||
rest_of_compilation (fndecl);
|
||
|
||
current_function_decl = 0;
|
||
}
|