diff options
author | Marc Espie <espie@cvs.openbsd.org> | 1999-05-26 13:38:57 +0000 |
---|---|---|
committer | Marc Espie <espie@cvs.openbsd.org> | 1999-05-26 13:38:57 +0000 |
commit | 0126e157b87f137fc08dc7f46f6c291b9d06ac5d (patch) | |
tree | f8555e3e504eb82b4cd3cba5cec20ae4ce8124ff /gnu/egcs/gcc/integrate.c | |
parent | ff8e9a4356e55ed142306c3a375fa280800abc86 (diff) |
egcs projects compiler system
Exact copy of the snapshot, except for the removal of
texinfo/
gcc/ch/
libchill/
Diffstat (limited to 'gnu/egcs/gcc/integrate.c')
-rw-r--r-- | gnu/egcs/gcc/integrate.c | 3484 |
1 files changed, 3484 insertions, 0 deletions
diff --git a/gnu/egcs/gcc/integrate.c b/gnu/egcs/gcc/integrate.c new file mode 100644 index 00000000000..33a96c40f91 --- /dev/null +++ b/gnu/egcs/gcc/integrate.c @@ -0,0 +1,3484 @@ +/* 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; +} |