2008年11月27日木曜日

関数呼び出しを埋め込む

GCC は -finstrument-functions で任意のプロファイル関数を関数の enter/return 場所に仕込むことができる。

Graphvizによるファンクション・コールの視覚化

ちなみに、このプロファイル関数 (__cyg_profile_function_enter/exit()) は決め打ちで埋め込まれるので、C++ の場合は extern "C" で宣言しなければならない。

How to use '-finstrument-functions' in C++ programs ?

この機能の実装は、単に関数呼び出しの RTX を直接展開することで行われているようだ。

[tree-ssa][rfc] -finstrument-functions at tree level

gcc-4.3.2/gcc/builtins.c

static rtx
expand_builtin_profile_func (bool exitp)
{
rtx this, which;

this = DECL_RTL (current_function_decl);
gcc_assert (MEM_P (this));
this = XEXP (this, 0);

if (exitp)
which = profile_function_exit_libfunc;
else
which = profile_function_entry_libfunc;

emit_library_call (which, LCT_NORMAL, VOIDmode, 2, this, Pmode,
expand_builtin_return_addr (BUILT_IN_RETURN_ADDRESS,
0),
Pmode);

return const0_rtx;
}

gcc-4.3.2/gcc/libfuncs.h

#define profile_function_exit_libfunc (libfunc_table[LTI_profile_function_exit])

gcc-4.3.2/gcc/optabs.c

rtx libfunc_table[LTI_MAX];


rtx
init_one_libfunc (const char *name)
{
rtx symbol;

/* Create a FUNCTION_DECL that can be passed to
targetm.encode_section_info. */
/* ??? We don't have any type information except for this is
a function. Pretend this is "int foo()". */
tree decl = build_decl (FUNCTION_DECL, get_identifier (name),
build_function_type (integer_type_node, NULL_TREE));
DECL_ARTIFICIAL (decl) = 1;
DECL_EXTERNAL (decl) = 1;
TREE_PUBLIC (decl) = 1;

symbol = XEXP (DECL_RTL (decl), 0);

/* Zap the nonsensical SYMBOL_REF_DECL for this. What we're left with
are the flags assigned by targetm.encode_section_info. */
SET_SYMBOL_REF_DECL (symbol, 0);

return symbol;
}

単に external でかつ artificial な関数呼び出しを作っているようだ。

abort_libfunc = init_one_libfunc ("abort");
memcpy_libfunc = init_one_libfunc ("memcpy");
memmove_libfunc = init_one_libfunc ("memmove");
memcmp_libfunc = init_one_libfunc ("memcmp");
memset_libfunc = init_one_libfunc ("memset");
setbits_libfunc = init_one_libfunc ("__setbits");

#ifndef DONT_USE_BUILTIN_SETJMP
setjmp_libfunc = init_one_libfunc ("__builtin_setjmp");
longjmp_libfunc = init_one_libfunc ("__builtin_longjmp");
#else
setjmp_libfunc = init_one_libfunc ("setjmp");
longjmp_libfunc = init_one_libfunc ("longjmp");
#endif
unwind_sjlj_register_libfunc = init_one_libfunc ("_Unwind_SjLj_Register");
unwind_sjlj_unregister_libfunc
= init_one_libfunc ("_Unwind_SjLj_Unregister");

/* For function entry/exit instrumentation. */
profile_function_entry_libfunc
= init_one_libfunc ("__cyg_profile_func_enter");
profile_function_exit_libfunc
= init_one_libfunc ("__cyg_profile_func_exit");

gcov_flush_libfunc = init_one_libfunc ("__gcov_flush");

gcc-4.3.2/gcc/calls.h

/* Output a library call to function FUN (a SYMBOL_REF rtx)
(emitting the queue unless NO_QUEUE is nonzero),
for a value of mode OUTMODE,
with NARGS different arguments, passed as alternating rtx values
and machine_modes to convert them to.

FN_TYPE should be LCT_NORMAL for `normal' calls, LCT_CONST for `const'
calls, LCT_PURE for `pure' calls, LCT_CONST_MAKE_BLOCK for `const' calls
which should be enclosed in REG_LIBCALL/REG_RETVAL notes,
LCT_PURE_MAKE_BLOCK for `purep' calls which should be enclosed in
REG_LIBCALL/REG_RETVAL notes with extra (use (memory (scratch)),
or other LCT_ value for other types of library calls. */

void
emit_library_call (rtx orgfun, enum libcall_type fn_type,
enum machine_mode outmode, int nargs, ...)
{
va_list p;

va_start (p, nargs);
emit_library_call_value_1 (0, orgfun, NULL_RTX, fn_type, outmode, nargs, p);
va_end (p);
}

同ファイル内の emit_library_call_value_1 は非常に巨大。

/* Output a library call to function FUN (a SYMBOL_REF rtx).
The RETVAL parameter specifies whether return value needs to be saved, other
parameters are documented in the emit_library_call function below. */

static rtx
emit_library_call_value_1 (int retval, rtx orgfun, rtx value,
enum libcall_type fn_type,
enum machine_mode outmode, int nargs, va_list p)
{
/* Total size in bytes of all the stack-parms scanned so far. */
struct args_size args_size;
/* Size of arguments before any adjustments (such as rounding). */
struct args_size original_args_size;
int argnum;
rtx fun;
int inc;
int count;
rtx argblock = 0;
CUMULATIVE_ARGS args_so_far;
struct arg
{
rtx value;
enum machine_mode mode;
rtx reg;
int partial;
struct locate_and_pad_arg_data locate;
rtx save_area;
};
struct arg *argvec;
int old_inhibit_defer_pop = inhibit_defer_pop;
rtx call_fusage = 0;
rtx mem_value = 0;
rtx valreg;
int pcc_struct_value = 0;
int struct_value_size = 0;
int flags;
int reg_parm_stack_space = 0;
int needed;
rtx before_call;
tree tfom; /* type_for_mode (outmode, 0) */

#ifdef REG_PARM_STACK_SPACE
/* Define the boundary of the register parm stack space that needs to be
save, if any. */
int low_to_save, high_to_save;
rtx save_area = 0; /* Place that it is saved. */
#endif

/* Size of the stack reserved for parameter registers. */
int initial_highest_arg_in_use = highest_outgoing_arg_in_use;
char *initial_stack_usage_map = stack_usage_map;
char *stack_usage_map_buf = NULL;

rtx struct_value = targetm.calls.struct_value_rtx (0, 0);

#ifdef REG_PARM_STACK_SPACE
reg_parm_stack_space = REG_PARM_STACK_SPACE ((tree) 0);
#endif

/* By default, library functions can not throw. */
flags = ECF_NOTHROW;

switch (fn_type)
{
case LCT_NORMAL:
break;
case LCT_CONST:
flags |= ECF_CONST;
break;
case LCT_PURE:
flags |= ECF_PURE;
break;
case LCT_CONST_MAKE_BLOCK:
flags |= ECF_CONST | ECF_LIBCALL_BLOCK;
break;
case LCT_PURE_MAKE_BLOCK:
flags |= ECF_PURE | ECF_LIBCALL_BLOCK;
break;
case LCT_NORETURN:
flags |= ECF_NORETURN;
break;
case LCT_THROW:
flags = ECF_NORETURN;
break;
case LCT_RETURNS_TWICE:
flags = ECF_RETURNS_TWICE;
break;
}
fun = orgfun;

/* Ensure current function's preferred stack boundary is at least
what we need. */
if (cfun->preferred_stack_boundary < PREFERRED_STACK_BOUNDARY)
cfun->preferred_stack_boundary = PREFERRED_STACK_BOUNDARY;

/* If this kind of value comes back in memory,
decide where in memory it should come back. */
if (outmode != VOIDmode)
{
tfom = lang_hooks.types.type_for_mode (outmode, 0);
if (aggregate_value_p (tfom, 0))
{
#ifdef PCC_STATIC_STRUCT_RETURN
rtx pointer_reg
= hard_function_value (build_pointer_type (tfom), 0, 0, 0);
mem_value = gen_rtx_MEM (outmode, pointer_reg);
pcc_struct_value = 1;
if (value == 0)
value = gen_reg_rtx (outmode);
#else /* not PCC_STATIC_STRUCT_RETURN */
struct_value_size = GET_MODE_SIZE (outmode);
if (value != 0 && MEM_P (value))
mem_value = value;
else
mem_value = assign_temp (tfom, 0, 1, 1);
#endif
/* This call returns a big structure. */
flags &= ~(ECF_CONST | ECF_PURE | ECF_LIBCALL_BLOCK);
}
}
else
tfom = void_type_node;

/* ??? Unfinished: must pass the memory address as an argument. */

/* Copy all the libcall-arguments out of the varargs data
and into a vector ARGVEC.

Compute how to pass each argument. We only support a very small subset
of the full argument passing conventions to limit complexity here since
library functions shouldn't have many args. */

argvec = alloca ((nargs + 1) * sizeof (struct arg));
memset (argvec, 0, (nargs + 1) * sizeof (struct arg));

#ifdef INIT_CUMULATIVE_LIBCALL_ARGS
INIT_CUMULATIVE_LIBCALL_ARGS (args_so_far, outmode, fun);
#else
INIT_CUMULATIVE_ARGS (args_so_far, NULL_TREE, fun, 0, nargs);
#endif

args_size.constant = 0;
args_size.var = 0;

count = 0;

/* Now we are about to start emitting insns that can be deleted
if a libcall is deleted. */
if (flags & ECF_LIBCALL_BLOCK)
start_sequence ();

push_temp_slots ();

/* If there's a structure value address to be passed,
either pass it in the special place, or pass it as an extra argument. */
if (mem_value && struct_value == 0 && ! pcc_struct_value)
{
rtx addr = XEXP (mem_value, 0);

nargs++;

/* Make sure it is a reasonable operand for a move or push insn. */
if (!REG_P (addr) && !MEM_P (addr)
&& ! (CONSTANT_P (addr) && LEGITIMATE_CONSTANT_P (addr)))
addr = force_operand (addr, NULL_RTX);

argvec[count].value = addr;
argvec[count].mode = Pmode;
argvec[count].partial = 0;

argvec[count].reg = FUNCTION_ARG (args_so_far, Pmode, NULL_TREE, 1);
gcc_assert (targetm.calls.arg_partial_bytes (&args_so_far, Pmode,
NULL_TREE, 1) == 0);

locate_and_pad_parm (Pmode, NULL_TREE,
#ifdef STACK_PARMS_IN_REG_PARM_AREA
1,
#else
argvec[count].reg != 0,
#endif
0, NULL_TREE, &args_size, &argvec[count].locate);

if (argvec[count].reg == 0 || argvec[count].partial != 0
|| reg_parm_stack_space > 0)
args_size.constant += argvec[count].locate.size.constant;

FUNCTION_ARG_ADVANCE (args_so_far, Pmode, (tree) 0, 1);

count++;
}

for (; count < nargs; count++)
{
rtx val = va_arg (p, rtx);
enum machine_mode mode = va_arg (p, enum machine_mode);

/* We cannot convert the arg value to the mode the library wants here;
must do it earlier where we know the signedness of the arg. */
gcc_assert (mode != BLKmode
&& (GET_MODE (val) == mode || GET_MODE (val) == VOIDmode));

/* Make sure it is a reasonable operand for a move or push insn. */
if (!REG_P (val) && !MEM_P (val)
&& ! (CONSTANT_P (val) && LEGITIMATE_CONSTANT_P (val)))
val = force_operand (val, NULL_RTX);

if (pass_by_reference (&args_so_far, mode, NULL_TREE, 1))
{
rtx slot;
int must_copy
= !reference_callee_copied (&args_so_far, mode, NULL_TREE, 1);

/* loop.c won't look at CALL_INSN_FUNCTION_USAGE of const/pure
functions, so we have to pretend this isn't such a function. */
if (flags & ECF_LIBCALL_BLOCK)
{
rtx insns = get_insns ();
end_sequence ();
emit_insn (insns);
}
flags &= ~(ECF_CONST | ECF_PURE | ECF_LIBCALL_BLOCK);

/* If this was a CONST function, it is now PURE since
it now reads memory. */
if (flags & ECF_CONST)
{
flags &= ~ECF_CONST;
flags |= ECF_PURE;
}

if (GET_MODE (val) == MEM && !must_copy)
slot = val;
else
{
slot = assign_temp (lang_hooks.types.type_for_mode (mode, 0),
0, 1, 1);
emit_move_insn (slot, val);
}

call_fusage = gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_USE (VOIDmode, slot),
call_fusage);
if (must_copy)
call_fusage = gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_CLOBBER (VOIDmode,
slot),
call_fusage);

mode = Pmode;
val = force_operand (XEXP (slot, 0), NULL_RTX);
}

argvec[count].value = val;
argvec[count].mode = mode;

argvec[count].reg = FUNCTION_ARG (args_so_far, mode, NULL_TREE, 1);

argvec[count].partial
= targetm.calls.arg_partial_bytes (&args_so_far, mode, NULL_TREE, 1);

locate_and_pad_parm (mode, NULL_TREE,
#ifdef STACK_PARMS_IN_REG_PARM_AREA
1,
#else
argvec[count].reg != 0,
#endif
argvec[count].partial,
NULL_TREE, &args_size, &argvec[count].locate);

gcc_assert (!argvec[count].locate.size.var);

if (argvec[count].reg == 0 || argvec[count].partial != 0
|| reg_parm_stack_space > 0)
args_size.constant += argvec[count].locate.size.constant;

FUNCTION_ARG_ADVANCE (args_so_far, mode, (tree) 0, 1);
}

/* If this machine requires an external definition for library
functions, write one out. */
assemble_external_libcall (fun);

original_args_size = args_size;
args_size.constant = (((args_size.constant
+ stack_pointer_delta
+ STACK_BYTES - 1)
/ STACK_BYTES
* STACK_BYTES)
- stack_pointer_delta);

args_size.constant = MAX (args_size.constant,
reg_parm_stack_space);

if (!OUTGOING_REG_PARM_STACK_SPACE)
args_size.constant -= reg_parm_stack_space;

if (args_size.constant > current_function_outgoing_args_size)
current_function_outgoing_args_size = args_size.constant;

if (ACCUMULATE_OUTGOING_ARGS)
{
/* Since the stack pointer will never be pushed, it is possible for
the evaluation of a parm to clobber something we have already
written to the stack. Since most function calls on RISC machines
do not use the stack, this is uncommon, but must work correctly.

Therefore, we save any area of the stack that was already written
and that we are using. Here we set up to do this by making a new
stack usage map from the old one.

Another approach might be to try to reorder the argument
evaluations to avoid this conflicting stack usage. */

needed = args_size.constant;

/* Since we will be writing into the entire argument area, the
map must be allocated for its entire size, not just the part that
is the responsibility of the caller. */
if (!OUTGOING_REG_PARM_STACK_SPACE)
needed += reg_parm_stack_space;

#ifdef ARGS_GROW_DOWNWARD
highest_outgoing_arg_in_use = MAX (initial_highest_arg_in_use,
needed + 1);
#else
highest_outgoing_arg_in_use = MAX (initial_highest_arg_in_use,
needed);
#endif
stack_usage_map_buf = XNEWVEC (char, highest_outgoing_arg_in_use);
stack_usage_map = stack_usage_map_buf;

if (initial_highest_arg_in_use)
memcpy (stack_usage_map, initial_stack_usage_map,
initial_highest_arg_in_use);

if (initial_highest_arg_in_use != highest_outgoing_arg_in_use)
memset (&stack_usage_map[initial_highest_arg_in_use], 0,
highest_outgoing_arg_in_use - initial_highest_arg_in_use);
needed = 0;

/* We must be careful to use virtual regs before they're instantiated,
and real regs afterwards. Loop optimization, for example, can create
new libcalls after we've instantiated the virtual regs, and if we
use virtuals anyway, they won't match the rtl patterns. */

if (virtuals_instantiated)
argblock = plus_constant (stack_pointer_rtx, STACK_POINTER_OFFSET);
else
argblock = virtual_outgoing_args_rtx;
}
else
{
if (!PUSH_ARGS)
argblock = push_block (GEN_INT (args_size.constant), 0, 0);
}

/* If we push args individually in reverse order, perform stack alignment
before the first push (the last arg). */
if (argblock == 0 && PUSH_ARGS_REVERSED)
anti_adjust_stack (GEN_INT (args_size.constant
- original_args_size.constant));

if (PUSH_ARGS_REVERSED)
{
inc = -1;
argnum = nargs - 1;
}
else
{
inc = 1;
argnum = 0;
}

#ifdef REG_PARM_STACK_SPACE
if (ACCUMULATE_OUTGOING_ARGS)
{
/* The argument list is the property of the called routine and it
may clobber it. If the fixed area has been used for previous
parameters, we must save and restore it. */
save_area = save_fixed_argument_area (reg_parm_stack_space, argblock,
&low_to_save, &high_to_save);
}
#endif

/* Push the args that need to be pushed. */

/* ARGNUM indexes the ARGVEC array in the order in which the arguments
are to be pushed. */
for (count = 0; count < nargs; count++, argnum += inc)
{
enum machine_mode mode = argvec[argnum].mode;
rtx val = argvec[argnum].value;
rtx reg = argvec[argnum].reg;
int partial = argvec[argnum].partial;
int lower_bound = 0, upper_bound = 0, i;

if (! (reg != 0 && partial == 0))
{
if (ACCUMULATE_OUTGOING_ARGS)
{
/* If this is being stored into a pre-allocated, fixed-size,
stack area, save any previous data at that location. */

#ifdef ARGS_GROW_DOWNWARD
/* stack_slot is negative, but we want to index stack_usage_map
with positive values. */
upper_bound = -argvec[argnum].locate.offset.constant + 1;
lower_bound = upper_bound - argvec[argnum].locate.size.constant;
#else
lower_bound = argvec[argnum].locate.offset.constant;
upper_bound = lower_bound + argvec[argnum].locate.size.constant;
#endif

i = lower_bound;
/* Don't worry about things in the fixed argument area;
it has already been saved. */
if (i < reg_parm_stack_space)
i = reg_parm_stack_space;
while (i < upper_bound && stack_usage_map[i] == 0)
i++;

if (i < upper_bound)
{
/* We need to make a save area. */
unsigned int size
= argvec[argnum].locate.size.constant * BITS_PER_UNIT;
enum machine_mode save_mode
= mode_for_size (size, MODE_INT, 1);
rtx adr
= plus_constant (argblock,
argvec[argnum].locate.offset.constant);
rtx stack_area
= gen_rtx_MEM (save_mode, memory_address (save_mode, adr));

if (save_mode == BLKmode)
{
argvec[argnum].save_area
= assign_stack_temp (BLKmode,
argvec[argnum].locate.size.constant,
0);

emit_block_move (validize_mem (argvec[argnum].save_area),
stack_area,
GEN_INT (argvec[argnum].locate.size.constant),
BLOCK_OP_CALL_PARM);
}
else
{
argvec[argnum].save_area = gen_reg_rtx (save_mode);

emit_move_insn (argvec[argnum].save_area, stack_area);
}
}
}

emit_push_insn (val, mode, NULL_TREE, NULL_RTX, PARM_BOUNDARY,
partial, reg, 0, argblock,
GEN_INT (argvec[argnum].locate.offset.constant),
reg_parm_stack_space,
ARGS_SIZE_RTX (argvec[argnum].locate.alignment_pad));

/* Now mark the segment we just used. */
if (ACCUMULATE_OUTGOING_ARGS)
for (i = lower_bound; i < upper_bound; i++)
stack_usage_map[i] = 1;

NO_DEFER_POP;

if (flags & ECF_CONST)
{
rtx use;

/* Indicate argument access so that alias.c knows that these
values are live. */
if (argblock)
use = plus_constant (argblock,
argvec[argnum].locate.offset.constant);
else
/* When arguments are pushed, trying to tell alias.c where
exactly this argument is won't work, because the
auto-increment causes confusion. So we merely indicate
that we access something with a known mode somewhere on
the stack. */
use = gen_rtx_PLUS (Pmode, virtual_outgoing_args_rtx,
gen_rtx_SCRATCH (Pmode));
use = gen_rtx_MEM (argvec[argnum].mode, use);
use = gen_rtx_USE (VOIDmode, use);
call_fusage = gen_rtx_EXPR_LIST (VOIDmode, use, call_fusage);
}
}
}

/* If we pushed args in forward order, perform stack alignment
after pushing the last arg. */
if (argblock == 0 && !PUSH_ARGS_REVERSED)
anti_adjust_stack (GEN_INT (args_size.constant
- original_args_size.constant));

if (PUSH_ARGS_REVERSED)
argnum = nargs - 1;
else
argnum = 0;

fun = prepare_call_address (fun, NULL, &call_fusage, 0, 0);

/* Now load any reg parms into their regs. */

/* ARGNUM indexes the ARGVEC array in the order in which the arguments
are to be pushed. */
for (count = 0; count < nargs; count++, argnum += inc)
{
enum machine_mode mode = argvec[argnum].mode;
rtx val = argvec[argnum].value;
rtx reg = argvec[argnum].reg;
int partial = argvec[argnum].partial;

/* Handle calls that pass values in multiple non-contiguous
locations. The PA64 has examples of this for library calls. */
if (reg != 0 && GET_CODE (reg) == PARALLEL)
emit_group_load (reg, val, NULL_TREE, GET_MODE_SIZE (mode));
else if (reg != 0 && partial == 0)
emit_move_insn (reg, val);

NO_DEFER_POP;
}

/* Any regs containing parms remain in use through the call. */
for (count = 0; count < nargs; count++)
{
rtx reg = argvec[count].reg;
if (reg != 0 && GET_CODE (reg) == PARALLEL)
use_group_regs (&call_fusage, reg);
else if (reg != 0)
{
int partial = argvec[count].partial;
if (partial)
{
int nregs;
gcc_assert (partial % UNITS_PER_WORD == 0);
nregs = partial / UNITS_PER_WORD;
use_regs (&call_fusage, REGNO (reg), nregs);
}
else
use_reg (&call_fusage, reg);
}
}

/* Pass the function the address in which to return a structure value. */
if (mem_value != 0 && struct_value != 0 && ! pcc_struct_value)
{
emit_move_insn (struct_value,
force_reg (Pmode,
force_operand (XEXP (mem_value, 0),
NULL_RTX)));
if (REG_P (struct_value))
use_reg (&call_fusage, struct_value);
}

/* Don't allow popping to be deferred, since then
cse'ing of library calls could delete a call and leave the pop. */
NO_DEFER_POP;
valreg = (mem_value == 0 && outmode != VOIDmode
? hard_libcall_value (outmode) : NULL_RTX);

/* Stack must be properly aligned now. */
gcc_assert (!(stack_pointer_delta
& (PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT - 1)));

before_call = get_last_insn ();

/* We pass the old value of inhibit_defer_pop + 1 to emit_call_1, which
will set inhibit_defer_pop to that value. */
/* The return type is needed to decide how many bytes the function pops.
Signedness plays no role in that, so for simplicity, we pretend it's
always signed. We also assume that the list of arguments passed has
no impact, so we pretend it is unknown. */

emit_call_1 (fun, NULL,
get_identifier (XSTR (orgfun, 0)),
build_function_type (tfom, NULL_TREE),
original_args_size.constant, args_size.constant,
struct_value_size,
FUNCTION_ARG (args_so_far, VOIDmode, void_type_node, 1),
valreg,
old_inhibit_defer_pop + 1, call_fusage, flags, & args_so_far);

/* For calls to `setjmp', etc., inform function.c:setjmp_warnings
that it should complain if nonvolatile values are live. For
functions that cannot return, inform flow that control does not
fall through. */

if (flags & ECF_NORETURN)
{
/* The barrier note must be emitted
immediately after the CALL_INSN. Some ports emit more than
just a CALL_INSN above, so we must search for it here. */

rtx last = get_last_insn ();
while (!CALL_P (last))
{
last = PREV_INSN (last);
/* There was no CALL_INSN? */
gcc_assert (last != before_call);
}

emit_barrier_after (last);
}

/* Now restore inhibit_defer_pop to its actual original value. */
OK_DEFER_POP;

/* If call is cse'able, make appropriate pair of reg-notes around it.
Test valreg so we don't crash; may safely ignore `const'
if return type is void. Disable for PARALLEL return values, because
we have no way to move such values into a pseudo register. */
if (flags & ECF_LIBCALL_BLOCK)
{
rtx insns;

if (valreg == 0)
{
insns = get_insns ();
end_sequence ();
emit_insn (insns);
}
else
{
rtx note = 0;
rtx temp;
int i;

if (GET_CODE (valreg) == PARALLEL)
{
temp = gen_reg_rtx (outmode);
emit_group_store (temp, valreg, NULL_TREE,
GET_MODE_SIZE (outmode));
valreg = temp;
}

temp = gen_reg_rtx (GET_MODE (valreg));

/* Construct an "equal form" for the value which mentions all the
arguments in order as well as the function name. */
for (i = 0; i < nargs; i++)
note = gen_rtx_EXPR_LIST (VOIDmode, argvec[i].value, note);
note = gen_rtx_EXPR_LIST (VOIDmode, fun, note);

insns = get_insns ();
end_sequence ();

if (flags & ECF_PURE)
note = gen_rtx_EXPR_LIST (VOIDmode,
gen_rtx_USE (VOIDmode,
gen_rtx_MEM (BLKmode,
gen_rtx_SCRATCH (VOIDmode))),
note);

emit_libcall_block (insns, temp, valreg, note);

valreg = temp;
}
}
pop_temp_slots ();

/* Copy the value to the right place. */
if (outmode != VOIDmode && retval)
{
if (mem_value)
{
if (value == 0)
value = mem_value;
if (value != mem_value)
emit_move_insn (value, mem_value);
}
else if (GET_CODE (valreg) == PARALLEL)
{
if (value == 0)
value = gen_reg_rtx (outmode);
emit_group_store (value, valreg, NULL_TREE, GET_MODE_SIZE (outmode));
}
else
{
/* Convert to the proper mode if PROMOTE_MODE has been active. */
if (GET_MODE (valreg) != outmode)
{
int unsignedp = TYPE_UNSIGNED (tfom);

gcc_assert (targetm.calls.promote_function_return (tfom));
gcc_assert (promote_mode (tfom, outmode, &unsignedp, 0)
== GET_MODE (valreg));

valreg = convert_modes (outmode, GET_MODE (valreg), valreg, 0);
}

if (value != 0)
emit_move_insn (value, valreg);
else
value = valreg;
}
}

if (ACCUMULATE_OUTGOING_ARGS)
{
#ifdef REG_PARM_STACK_SPACE
if (save_area)
restore_fixed_argument_area (save_area, argblock,
high_to_save, low_to_save);
#endif

/* If we saved any argument areas, restore them. */
for (count = 0; count < nargs; count++)
if (argvec[count].save_area)
{
enum machine_mode save_mode = GET_MODE (argvec[count].save_area);
rtx adr = plus_constant (argblock,
argvec[count].locate.offset.constant);
rtx stack_area = gen_rtx_MEM (save_mode,
memory_address (save_mode, adr));

if (save_mode == BLKmode)
emit_block_move (stack_area,
validize_mem (argvec[count].save_area),
GEN_INT (argvec[count].locate.size.constant),
BLOCK_OP_CALL_PARM);
else
emit_move_insn (stack_area, argvec[count].save_area);
}

highest_outgoing_arg_in_use = initial_highest_arg_in_use;
stack_usage_map = initial_stack_usage_map;
}

if (stack_usage_map_buf)
free (stack_usage_map_buf);

return value;

}

0 件のコメント: