#include "config.h"
#include "system.h"
#include "rtl.h"
#include "regs.h"
#include "function.h"
#include "hard-reg-set.h"
#include "flags.h"
#include "insn-config.h"
#include "recog.h"
#include "basic-block.h"
#include "output.h"
#include "except.h"
#include "tree.h"
static rtx return_value_pseudo;
static int identify_call_return_value PARAMS ((rtx, rtx *, rtx *));
static rtx skip_copy_to_return_value PARAMS ((rtx));
static rtx skip_use_of_return_value PARAMS ((rtx, enum rtx_code));
static rtx skip_stack_adjustment PARAMS ((rtx));
static rtx skip_pic_restore PARAMS ((rtx));
static rtx skip_jump_insn PARAMS ((rtx));
static int call_ends_block_p PARAMS ((rtx, rtx));
static int uses_addressof PARAMS ((rtx));
static int sequence_uses_addressof PARAMS ((rtx));
static void purge_reg_equiv_notes PARAMS ((void));
static void purge_mem_unchanging_flag PARAMS ((rtx));
static rtx skip_unreturned_value PARAMS ((rtx));
static int
identify_call_return_value (cp, p_hard_return, p_soft_return)
rtx cp;
rtx *p_hard_return, *p_soft_return;
{
rtx insn, set, hard, soft;
insn = XEXP (cp, 0);
while (NEXT_INSN (insn))
insn = NEXT_INSN (insn);
while (GET_CODE (insn) != CALL_INSN)
insn = PREV_INSN (insn);
if (GET_CODE (PATTERN (insn)) == SET
&& GET_CODE (SET_SRC (PATTERN (insn))) == CALL)
hard = SET_DEST (PATTERN (insn));
else if (GET_CODE (PATTERN (insn)) == PARALLEL
&& GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == SET
&& GET_CODE (SET_SRC (XVECEXP (PATTERN (insn), 0, 0))) == CALL)
hard = SET_DEST (XVECEXP (PATTERN (insn), 0, 0));
else
return 0;
if (GET_CODE (hard) != REG)
return 0;
insn = skip_stack_adjustment (insn);
if (! insn)
return 0;
insn = skip_pic_restore (insn);
if (! insn)
return 0;
insn = NEXT_INSN (insn);
if (! insn)
return 0;
set = single_set (insn);
if (! set || SET_SRC (set) != hard)
return 0;
soft = SET_DEST (set);
insn = NEXT_INSN (insn);
if (insn
&& (set = single_set (insn)) != NULL_RTX
&& SET_SRC (set) == soft)
{
soft = SET_DEST (set);
insn = NEXT_INSN (insn);
}
if (GET_CODE (soft) != REG || REGNO (soft) < FIRST_PSEUDO_REGISTER)
return 0;
if (reg_set_between_p (soft, insn, NULL_RTX))
return 0;
*p_hard_return = hard;
*p_soft_return = soft;
return 1;
}
static rtx
skip_copy_to_return_value (orig_insn)
rtx orig_insn;
{
rtx insn, set = NULL_RTX;
rtx hardret, softret;
if (! identify_call_return_value (PATTERN (orig_insn), &hardret, &softret))
return orig_insn;
insn = next_nonnote_insn (orig_insn);
if (! insn)
return orig_insn;
set = single_set (insn);
if (! set)
return orig_insn;
if (return_value_pseudo)
{
if (SET_DEST (set) == return_value_pseudo
&& SET_SRC (set) == softret)
return insn;
return orig_insn;
}
#ifndef OUTGOING_REGNO
#define OUTGOING_REGNO(N) (N)
#endif
if (SET_DEST (set) == current_function_return_rtx
&& REG_P (SET_DEST (set))
&& OUTGOING_REGNO (REGNO (SET_DEST (set))) == REGNO (hardret)
&& SET_SRC (set) == softret)
return insn;
if (REG_P (SET_DEST (set))
&& SET_SRC (set) == softret)
{
rtx x = SET_DEST (set);
insn = next_nonnote_insn (insn);
if (! insn)
return orig_insn;
set = single_set (insn);
if (! set)
return orig_insn;
if (SET_DEST (set) == current_function_return_rtx
&& REG_P (SET_DEST (set))
&& OUTGOING_REGNO (REGNO (SET_DEST (set))) == REGNO (hardret)
&& SET_SRC (set) == x)
return insn;
}
return orig_insn;
}
static rtx
skip_use_of_return_value (orig_insn, code)
rtx orig_insn;
enum rtx_code code;
{
rtx insn;
insn = next_nonnote_insn (orig_insn);
if (insn
&& GET_CODE (insn) == INSN
&& GET_CODE (PATTERN (insn)) == code
&& (XEXP (PATTERN (insn), 0) == current_function_return_rtx
|| XEXP (PATTERN (insn), 0) == const0_rtx))
return insn;
return orig_insn;
}
static rtx
skip_unreturned_value (orig_insn)
rtx orig_insn;
{
rtx insn = next_nonnote_insn (orig_insn);
if (insn
&& GET_CODE (insn) == INSN
&& GET_CODE (PATTERN (insn)) == CLOBBER
&& REG_P (XEXP (PATTERN (insn), 0))
&& (REGNO (XEXP (PATTERN (insn), 0)) >= FIRST_PSEUDO_REGISTER))
{
rtx set_insn = next_nonnote_insn (insn);
rtx set;
if (!set_insn)
return insn;
set = single_set (set_insn);
if (!set
|| SET_SRC (set) != XEXP (PATTERN (insn), 0)
|| SET_DEST (set) != current_function_return_rtx)
return insn;
return set_insn;
}
return orig_insn;
}
static rtx
skip_stack_adjustment (orig_insn)
rtx orig_insn;
{
rtx insn, set = NULL_RTX;
insn = next_nonnote_insn (orig_insn);
if (insn)
set = single_set (insn);
if (insn
&& set
&& GET_CODE (SET_SRC (set)) == PLUS
&& XEXP (SET_SRC (set), 0) == stack_pointer_rtx
&& GET_CODE (XEXP (SET_SRC (set), 1)) == CONST_INT
&& SET_DEST (set) == stack_pointer_rtx)
return insn;
return orig_insn;
}
static rtx
skip_pic_restore (orig_insn)
rtx orig_insn;
{
rtx insn, set = NULL_RTX;
insn = next_nonnote_insn (orig_insn);
if (insn)
set = single_set (insn);
if (insn && set && SET_DEST (set) == pic_offset_table_rtx)
return insn;
return orig_insn;
}
static rtx
skip_jump_insn (orig_insn)
rtx orig_insn;
{
rtx insn;
insn = next_nonnote_insn (orig_insn);
if (insn
&& GET_CODE (insn) == JUMP_INSN
&& any_uncondjump_p (insn))
return insn;
return orig_insn;
}
static int
call_ends_block_p (insn, end)
rtx insn;
rtx end;
{
rtx new_insn;
end = next_nonnote_insn (PREV_INSN (end));
if (insn == end)
return 1;
new_insn = skip_copy_to_return_value (insn);
if (return_value_pseudo && insn == new_insn)
return 0;
insn = new_insn;
if (insn == end)
return 1;
insn = skip_stack_adjustment (insn);
if (insn == end)
return 1;
insn = skip_use_of_return_value (insn, CLOBBER);
if (insn == end)
return 1;
insn = skip_unreturned_value (insn);
if (insn == end)
return 1;
insn = skip_use_of_return_value (insn, USE);
if (insn == end)
return 1;
insn = skip_jump_insn (insn);
return insn == end;
}
static int
uses_addressof (x)
rtx x;
{
RTX_CODE code;
int i, j;
const char *fmt;
if (x == NULL_RTX)
return 0;
code = GET_CODE (x);
if (code == ADDRESSOF || x == current_function_internal_arg_pointer)
return 1;
if (code == MEM)
return 0;
fmt = GET_RTX_FORMAT (code);
for (i = 0; i < GET_RTX_LENGTH (code); i++, fmt++)
{
if (*fmt == 'e')
{
if (uses_addressof (XEXP (x, i)))
return 1;
}
else if (*fmt == 'E')
{
for (j = 0; j < XVECLEN (x, i); j++)
if (uses_addressof (XVECEXP (x, i, j)))
return 1;
}
}
return 0;
}
static int
sequence_uses_addressof (seq)
rtx seq;
{
rtx insn;
for (insn = seq; insn; insn = NEXT_INSN (insn))
if (INSN_P (insn))
{
if (GET_CODE (insn) == CALL_INSN
&& GET_CODE (PATTERN (insn)) == CALL_PLACEHOLDER)
{
if (XEXP (PATTERN (insn), 0) != NULL_RTX
&& sequence_uses_addressof (XEXP (PATTERN (insn), 0)))
return 1;
if (XEXP (PATTERN (insn), 1) != NULL_RTX
&& sequence_uses_addressof (XEXP (PATTERN (insn), 1)))
return 1;
if (XEXP (PATTERN (insn), 2) != NULL_RTX
&& sequence_uses_addressof (XEXP (PATTERN (insn), 2)))
return 1;
}
else if (uses_addressof (PATTERN (insn))
|| (REG_NOTES (insn) && uses_addressof (REG_NOTES (insn))))
return 1;
}
return 0;
}
static void
purge_reg_equiv_notes ()
{
rtx insn;
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
while (1)
{
rtx note = find_reg_note (insn, REG_EQUIV, 0);
if (note)
{
remove_note (insn, note);
continue;
}
break;
}
}
}
static void
purge_mem_unchanging_flag (x)
rtx x;
{
RTX_CODE code;
int i, j;
const char *fmt;
if (x == NULL_RTX)
return;
code = GET_CODE (x);
if (code == MEM)
{
if (RTX_UNCHANGING_P (x)
&& (XEXP (x, 0) == current_function_internal_arg_pointer
|| (GET_CODE (XEXP (x, 0)) == PLUS
&& XEXP (XEXP (x, 0), 0) ==
current_function_internal_arg_pointer
&& GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT)))
RTX_UNCHANGING_P (x) = 0;
return;
}
fmt = GET_RTX_FORMAT (code);
for (i = 0; i < GET_RTX_LENGTH (code); i++, fmt++)
{
if (*fmt == 'e')
purge_mem_unchanging_flag (XEXP (x, i));
else if (*fmt == 'E')
for (j = 0; j < XVECLEN (x, i); j++)
purge_mem_unchanging_flag (XVECEXP (x, i, j));
}
}
void
replace_call_placeholder (insn, use)
rtx insn;
sibcall_use_t use;
{
if (use == sibcall_use_tail_recursion)
emit_insns_before (XEXP (PATTERN (insn), 2), insn);
else if (use == sibcall_use_sibcall)
emit_insns_before (XEXP (PATTERN (insn), 1), insn);
else if (use == sibcall_use_normal)
emit_insns_before (XEXP (PATTERN (insn), 0), insn);
else
abort ();
if (XEXP (PATTERN (insn), 3))
LABEL_PRESERVE_P (XEXP (PATTERN (insn), 3)) = 0;
remove_insn (insn);
}
void
optimize_sibling_and_tail_recursive_calls ()
{
rtx insn, insns;
basic_block alternate_exit = EXIT_BLOCK_PTR;
bool no_sibcalls_this_function = false;
int successful_sibling_call = 0;
int replaced_call_placeholder = 0;
edge e;
insns = get_insns ();
find_exception_handler_labels ();
rebuild_jump_labels (insns);
find_basic_blocks (insns, max_reg_num (), 0);
cleanup_cfg (CLEANUP_PRE_SIBCALL | CLEANUP_PRE_LOOP);
if (n_basic_blocks == 0)
return;
if (USING_SJLJ_EXCEPTIONS && current_function_has_exception_handlers ())
no_sibcalls_this_function = true;
return_value_pseudo = NULL_RTX;
for (e = EXIT_BLOCK_PTR->pred;
e && alternate_exit == EXIT_BLOCK_PTR;
e = e->pred_next)
{
rtx insn;
if (e->dest != EXIT_BLOCK_PTR || e->succ_next != NULL)
continue;
for (insn = BLOCK_HEAD (n_basic_blocks - 1);
insn;
insn = NEXT_INSN (insn))
{
rtx set;
if (GET_CODE (insn) == CODE_LABEL)
continue;
if (GET_CODE (insn) == NOTE)
continue;
if (GET_CODE (insn) == INSN
&& GET_CODE (PATTERN (insn)) == USE)
continue;
if (GET_CODE (insn) == INSN
&& (set = single_set (insn))
&& SET_DEST (set) == current_function_return_rtx
&& REG_P (SET_SRC (set))
&& !return_value_pseudo)
{
return_value_pseudo = SET_SRC (set);
continue;
}
break;
}
if (insn == NULL)
alternate_exit = e->src;
else
return_value_pseudo = NULL;
}
no_sibcalls_this_function |= sequence_uses_addressof (insns);
for (insn = insns; insn; insn = NEXT_INSN (insn))
{
if (GET_CODE (insn) == CALL_INSN
&& GET_CODE (PATTERN (insn)) == CALL_PLACEHOLDER)
{
int sibcall = (XEXP (PATTERN (insn), 1) != NULL_RTX);
int tailrecursion = (XEXP (PATTERN (insn), 2) != NULL_RTX);
basic_block call_block = BLOCK_FOR_INSN (insn);
if (current_function_calls_alloca
|| current_function_varargs || current_function_stdarg)
sibcall = 0;
if (no_sibcalls_this_function
|| frame_offset
|| current_function_calls_setjmp
|| call_block->succ == NULL
|| call_block->succ->succ_next != NULL
|| (call_block->succ->dest != EXIT_BLOCK_PTR
&& call_block->succ->dest != alternate_exit)
|| ! call_ends_block_p (insn, call_block->end))
sibcall = 0, tailrecursion = 0;
if (sibcall)
successful_sibling_call = 1;
replaced_call_placeholder = 1;
replace_call_placeholder (insn,
tailrecursion != 0
? sibcall_use_tail_recursion
: sibcall != 0
? sibcall_use_sibcall
: sibcall_use_normal);
}
}
if (successful_sibling_call)
{
rtx insn;
tree arg;
purge_reg_equiv_notes ();
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (INSN_P (insn))
purge_mem_unchanging_flag (PATTERN (insn));
}
for (arg = DECL_ARGUMENTS (current_function_decl);
arg;
arg = TREE_CHAIN (arg))
{
if (REG_P (DECL_RTL (arg)))
RTX_UNCHANGING_P (DECL_RTL (arg)) = false;
}
}
if (replaced_call_placeholder)
reorder_blocks ();
free_basic_block_vars (0);
}