Commit fa47911c by Jason Merrill Committed by Jason Merrill

re PR c++/19317 (removing a temporary return value when we cannot)

        PR c++/19317
        Leave the return slot target in the MODIFY_EXPR rather than making
        it an argument, but only use it if the CALL_EXPR has a flag set.
        * tree.h (CALL_EXPR_HAS_RETURN_SLOT_ADDR): Rename to
        CALL_EXPR_RETURN_SLOT_OPT.
        * calls.c (expand_call): Adjust.
        * tree-inline.c (expand_call_inline): Adjust.
        * tree-pretty-print.c (dump_generic_node): Adjust.

        And set the flag as appropriate.
        * gimplify.c (gimplify_modify_expr_rhs): Set
        CALL_EXPR_HAS_RETURN_SLOT_ADDR where the LHS is obviously safe.
        * tree-nrv.c (execute_return_slot_opt): Set
        CALL_EXPR_HAS_RETURN_SLOT_ADDR based on escape analysis.
        * tree-pass.h: Declare pass_return_slot.
        * tree-optimize.c (init_tree_optimization_passes): Add it.

        * cp/semantics.c (simplify_aggr_init_expr): Use
        CALL_EXPR_RETURN_SLOT_OPT, not CALL_EXPR_HAS_RETURN_SLOT_ADDR.

From-SVN: r101269
parent f565b0a1
2005-06-23 Jason Merrill <jason@redhat.com>
PR c++/19317
Leave the return slot target in the MODIFY_EXPR rather than making
it an argument, but only use it if the CALL_EXPR has a flag set.
* tree.h (CALL_EXPR_HAS_RETURN_SLOT_ADDR): Rename to
CALL_EXPR_RETURN_SLOT_OPT.
* calls.c (expand_call): Adjust.
* tree-inline.c (expand_call_inline): Adjust.
* tree-pretty-print.c (dump_generic_node): Adjust.
And set the flag as appropriate.
* gimplify.c (gimplify_modify_expr_rhs): Set
CALL_EXPR_HAS_RETURN_SLOT_ADDR where the LHS is obviously safe.
* tree-nrv.c (execute_return_slot_opt): Set
CALL_EXPR_HAS_RETURN_SLOT_ADDR based on escape analysis.
* tree-pass.h: Declare pass_return_slot.
* tree-optimize.c (init_tree_optimization_passes): Add it.
2005-06-23 David Edelsohn <edelsohn@gnu.org> 2005-06-23 David Edelsohn <edelsohn@gnu.org>
PR target/21760 PR target/21760
......
...@@ -1964,17 +1964,7 @@ expand_call (tree exp, rtx target, int ignore) ...@@ -1964,17 +1964,7 @@ expand_call (tree exp, rtx target, int ignore)
{ {
struct_value_size = int_size_in_bytes (TREE_TYPE (exp)); struct_value_size = int_size_in_bytes (TREE_TYPE (exp));
if (CALL_EXPR_HAS_RETURN_SLOT_ADDR (exp)) if (target && MEM_P (target) && CALL_EXPR_RETURN_SLOT_OPT (exp))
{
/* The structure value address arg is already in actparms.
Pull it out. It might be nice to just leave it there, but
we need to set structure_value_addr. */
tree return_arg = TREE_VALUE (actparms);
actparms = TREE_CHAIN (actparms);
structure_value_addr = expand_expr (return_arg, NULL_RTX,
VOIDmode, EXPAND_NORMAL);
}
else if (target && MEM_P (target))
structure_value_addr = XEXP (target, 0); structure_value_addr = XEXP (target, 0);
else else
{ {
......
2005-06-23 Jason Merrill <jason@redhat.com>
PR c++/19317
* semantics.c (simplify_aggr_init_expr): Use
CALL_EXPR_RETURN_SLOT_OPT, not CALL_EXPR_HAS_RETURN_SLOT_ADDR.
2005-06-23 Volker Reichelt <reichelt@igpm.rwth-aachen.de> 2005-06-23 Volker Reichelt <reichelt@igpm.rwth-aachen.de>
* pt.c (register_specialization): Remove superfluous assertion. * pt.c (register_specialization): Remove superfluous assertion.
......
...@@ -2904,27 +2904,15 @@ simplify_aggr_init_expr (tree *tp) ...@@ -2904,27 +2904,15 @@ simplify_aggr_init_expr (tree *tp)
style = arg; style = arg;
} }
if (style == ctor || style == arg) if (style == ctor)
{ {
/* Pass the address of the slot. If this is a constructor, we /* Replace the first argument to the ctor with the address of the
replace the first argument; otherwise, we tack on a new one. */ slot. */
tree addr; tree addr;
if (style == ctor) args = TREE_CHAIN (args);
args = TREE_CHAIN (args);
cxx_mark_addressable (slot); cxx_mark_addressable (slot);
addr = build1 (ADDR_EXPR, build_pointer_type (type), slot); addr = build1 (ADDR_EXPR, build_pointer_type (type), slot);
if (style == arg)
{
/* The return type might have different cv-quals from the slot. */
tree fntype = TREE_TYPE (TREE_TYPE (fn));
gcc_assert (TREE_CODE (fntype) == FUNCTION_TYPE
|| TREE_CODE (fntype) == METHOD_TYPE);
addr = convert (build_pointer_type (TREE_TYPE (fntype)), addr);
}
args = tree_cons (NULL_TREE, addr, args); args = tree_cons (NULL_TREE, addr, args);
} }
...@@ -2933,9 +2921,13 @@ simplify_aggr_init_expr (tree *tp) ...@@ -2933,9 +2921,13 @@ simplify_aggr_init_expr (tree *tp)
fn, args, NULL_TREE); fn, args, NULL_TREE);
if (style == arg) if (style == arg)
/* Tell the backend that we've added our return slot to the argument {
list. */ /* Just mark it addressable here, and leave the rest to
CALL_EXPR_HAS_RETURN_SLOT_ADDR (call_expr) = 1; expand_call{,_inline}. */
cxx_mark_addressable (slot);
CALL_EXPR_RETURN_SLOT_OPT (call_expr) = true;
call_expr = build2 (MODIFY_EXPR, TREE_TYPE (call_expr), slot, call_expr);
}
else if (style == pcc) else if (style == pcc)
{ {
/* If we're using the non-reentrant PCC calling convention, then we /* If we're using the non-reentrant PCC calling convention, then we
...@@ -2945,6 +2937,7 @@ simplify_aggr_init_expr (tree *tp) ...@@ -2945,6 +2937,7 @@ simplify_aggr_init_expr (tree *tp)
call_expr = build_aggr_init (slot, call_expr, call_expr = build_aggr_init (slot, call_expr,
DIRECT_BIND | LOOKUP_ONLYCONVERTING); DIRECT_BIND | LOOKUP_ONLYCONVERTING);
pop_deferring_access_checks (); pop_deferring_access_checks ();
call_expr = build (COMPOUND_EXPR, TREE_TYPE (slot), call_expr, slot);
} }
*tp = call_expr; *tp = call_expr;
......
...@@ -3015,6 +3015,46 @@ gimplify_modify_expr_rhs (tree *expr_p, tree *from_p, tree *to_p, tree *pre_p, ...@@ -3015,6 +3015,46 @@ gimplify_modify_expr_rhs (tree *expr_p, tree *from_p, tree *to_p, tree *pre_p,
ret = GS_UNHANDLED; ret = GS_UNHANDLED;
break; break;
case CALL_EXPR:
/* For calls that return in memory, give *to_p as the CALL_EXPR's
return slot so that we don't generate a temporary. */
if (!CALL_EXPR_RETURN_SLOT_OPT (*from_p)
&& aggregate_value_p (*from_p, *from_p))
{
bool use_target;
if (TREE_CODE (*to_p) == RESULT_DECL
&& needs_to_live_in_memory (*to_p))
/* It's always OK to use the return slot directly. */
use_target = true;
else if (!is_gimple_non_addressable (*to_p))
/* Don't use the original target if it's already addressable;
if its address escapes, and the called function uses the
NRV optimization, a conforming program could see *to_p
change before the called function returns; see c++/19317.
When optimizing, the return_slot pass marks more functions
as safe after we have escape info. */
use_target = false;
else if (DECL_GIMPLE_FORMAL_TEMP_P (*to_p))
/* Don't use the original target if it's a formal temp; we
don't want to take their addresses. */
use_target = false;
else if (is_gimple_reg_type (TREE_TYPE (*to_p)))
/* Also don't force regs into memory. */
use_target = false;
else
use_target = true;
if (use_target)
{
CALL_EXPR_RETURN_SLOT_OPT (*from_p) = 1;
lang_hooks.mark_addressable (*to_p);
}
}
ret = GS_UNHANDLED;
break;
default: default:
ret = GS_UNHANDLED; ret = GS_UNHANDLED;
break; break;
......
// PR c++/19317
// If we do both NRV and caller-side return slot opt for ma = ma.f,
// constructing fa incorrectly sets ma.i to 0.
extern "C" void abort();
struct A
{
int i;
int pad[32]; // force return in memory
A(): i(0) {}
A(int ia): i(ia) {}
};
A ga(42);
A f()
{
A la;
if (ga.i != 42)
abort();
return la;
}
int main()
{
ga = f ();
}
...@@ -2026,13 +2026,6 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data) ...@@ -2026,13 +2026,6 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data)
/* Initialize the parameters. */ /* Initialize the parameters. */
args = TREE_OPERAND (t, 1); args = TREE_OPERAND (t, 1);
if (CALL_EXPR_HAS_RETURN_SLOT_ADDR (t))
{
return_slot_addr = TREE_VALUE (args);
args = TREE_CHAIN (args);
}
else
return_slot_addr = NULL_TREE;
initialize_inlined_parameters (id, args, TREE_OPERAND (t, 2), fn, bb); initialize_inlined_parameters (id, args, TREE_OPERAND (t, 2), fn, bb);
...@@ -2046,10 +2039,10 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data) ...@@ -2046,10 +2039,10 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data)
gcc_assert (TREE_CODE (DECL_INITIAL (fn)) == BLOCK); gcc_assert (TREE_CODE (DECL_INITIAL (fn)) == BLOCK);
/* Find the lhs to which the result of this call is assigned. */ /* Find the lhs to which the result of this call is assigned. */
modify_dest = stmt; return_slot_addr = NULL;
if (TREE_CODE (modify_dest) == MODIFY_EXPR) if (TREE_CODE (stmt) == MODIFY_EXPR)
{ {
modify_dest = TREE_OPERAND (modify_dest, 0); modify_dest = TREE_OPERAND (stmt, 0);
/* The function which we are inlining might not return a value, /* The function which we are inlining might not return a value,
in which case we should issue a warning that the function in which case we should issue a warning that the function
...@@ -2059,6 +2052,11 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data) ...@@ -2059,6 +2052,11 @@ expand_call_inline (basic_block bb, tree stmt, tree *tp, void *data)
uninitialized variable. */ uninitialized variable. */
if (DECL_P (modify_dest)) if (DECL_P (modify_dest))
TREE_NO_WARNING (modify_dest) = 1; TREE_NO_WARNING (modify_dest) = 1;
if (CALL_EXPR_RETURN_SLOT_OPT (t))
{
return_slot_addr = build_fold_addr_expr (modify_dest);
modify_dest = NULL;
}
} }
else else
modify_dest = NULL; modify_dest = NULL;
......
...@@ -218,3 +218,79 @@ struct tree_opt_pass pass_nrv = ...@@ -218,3 +218,79 @@ struct tree_opt_pass pass_nrv =
TODO_dump_func | TODO_ggc_collect, /* todo_flags_finish */ TODO_dump_func | TODO_ggc_collect, /* todo_flags_finish */
0 /* letter */ 0 /* letter */
}; };
/* Walk through the function looking for MODIFY_EXPRs with calls that
return in memory on the RHS. For each of these, determine whether it is
safe to pass the address of the LHS as the return slot, and mark the
call appropriately if so.
The NRV shares the return slot with a local variable in the callee; this
optimization shares the return slot with the target of the call within
the caller. If the NRV is performed (which we can't know in general),
this optimization is safe if the address of the target has not
escaped prior to the call. If it has, modifications to the local
variable will produce visible changes elsewhere, as in PR c++/19317. */
static void
execute_return_slot_opt (void)
{
basic_block bb;
FOR_EACH_BB (bb)
{
block_stmt_iterator i;
for (i = bsi_start (bb); !bsi_end_p (i); bsi_next (&i))
{
tree stmt = bsi_stmt (i);
tree call;
if (TREE_CODE (stmt) == MODIFY_EXPR
&& (call = TREE_OPERAND (stmt, 1),
TREE_CODE (call) == CALL_EXPR)
&& !CALL_EXPR_RETURN_SLOT_OPT (call)
&& aggregate_value_p (call, call))
{
def_operand_p def_p;
ssa_op_iter op_iter;
/* We determine whether or not the LHS address escapes by
asking whether it is call clobbered. When the LHS isn't a
simple decl, we need to check the VDEFs, so it's simplest
to just loop through all the DEFs. */
FOR_EACH_SSA_DEF_OPERAND (def_p, stmt, op_iter, SSA_OP_ALL_DEFS)
{
tree def = DEF_FROM_PTR (def_p);
if (TREE_CODE (def) == SSA_NAME)
def = SSA_NAME_VAR (def);
if (is_call_clobbered (def))
goto unsafe;
}
/* No defs are call clobbered, so the optimization is safe. */
CALL_EXPR_RETURN_SLOT_OPT (call) = 1;
/* This is too late to mark the target addressable like we do
in gimplify_modify_expr_rhs, but that's OK; anything that
wasn't already addressable was handled there. */
unsafe:;
}
}
}
}
struct tree_opt_pass pass_return_slot =
{
"retslot", /* name */
NULL, /* gate */
execute_return_slot_opt, /* execute */
NULL, /* sub */
NULL, /* next */
0, /* static_pass_number */
0, /* tv_id */
PROP_ssa | PROP_alias, /* properties_required */
0, /* properties_provided */
0, /* properties_destroyed */
0, /* todo_flags_start */
0, /* todo_flags_finish */
0 /* letter */
};
...@@ -397,6 +397,7 @@ init_tree_optimization_passes (void) ...@@ -397,6 +397,7 @@ init_tree_optimization_passes (void)
NEXT_PASS (pass_build_ssa); NEXT_PASS (pass_build_ssa);
NEXT_PASS (pass_build_pta); NEXT_PASS (pass_build_pta);
NEXT_PASS (pass_may_alias); NEXT_PASS (pass_may_alias);
NEXT_PASS (pass_return_slot);
NEXT_PASS (pass_del_pta); NEXT_PASS (pass_del_pta);
NEXT_PASS (pass_rename_ssa_copies); NEXT_PASS (pass_rename_ssa_copies);
NEXT_PASS (pass_early_warn_uninitialized); NEXT_PASS (pass_early_warn_uninitialized);
......
...@@ -224,6 +224,7 @@ extern struct tree_opt_pass pass_create_structure_vars; ...@@ -224,6 +224,7 @@ extern struct tree_opt_pass pass_create_structure_vars;
extern struct tree_opt_pass pass_build_pta; extern struct tree_opt_pass pass_build_pta;
extern struct tree_opt_pass pass_del_pta; extern struct tree_opt_pass pass_del_pta;
extern struct tree_opt_pass pass_uncprop; extern struct tree_opt_pass pass_uncprop;
extern struct tree_opt_pass pass_return_slot;
extern struct tree_opt_pass pass_reassoc; extern struct tree_opt_pass pass_reassoc;
/* IPA Passes */ /* IPA Passes */
......
...@@ -1003,8 +1003,8 @@ dump_generic_node (pretty_printer *buffer, tree node, int spc, int flags, ...@@ -1003,8 +1003,8 @@ dump_generic_node (pretty_printer *buffer, tree node, int spc, int flags,
pp_character (buffer, ']'); pp_character (buffer, ']');
} }
if (CALL_EXPR_HAS_RETURN_SLOT_ADDR (node)) if (CALL_EXPR_RETURN_SLOT_OPT (node))
pp_string (buffer, " [return slot addr]"); pp_string (buffer, " [return slot optimization]");
if (CALL_EXPR_TAILCALL (node)) if (CALL_EXPR_TAILCALL (node))
pp_string (buffer, " [tail call]"); pp_string (buffer, " [tail call]");
break; break;
......
...@@ -337,7 +337,7 @@ struct tree_common GTY(()) ...@@ -337,7 +337,7 @@ struct tree_common GTY(())
TREE_PRIVATE in TREE_PRIVATE in
..._DECL ..._DECL
CALL_EXPR_HAS_RETURN_SLOT_ADDR in CALL_EXPR_RETURN_SLOT_OPT in
CALL_EXPR CALL_EXPR
DECL_BY_REFERENCE in DECL_BY_REFERENCE in
PARM_DECL, RESULT_DECL PARM_DECL, RESULT_DECL
...@@ -980,9 +980,9 @@ extern void tree_operand_check_failed (int, enum tree_code, ...@@ -980,9 +980,9 @@ extern void tree_operand_check_failed (int, enum tree_code,
an exception. In a CALL_EXPR, nonzero means the call cannot throw. */ an exception. In a CALL_EXPR, nonzero means the call cannot throw. */
#define TREE_NOTHROW(NODE) ((NODE)->common.nothrow_flag) #define TREE_NOTHROW(NODE) ((NODE)->common.nothrow_flag)
/* In a CALL_EXPR, means that the address of the return slot is part of the /* In a CALL_EXPR, means that it's safe to use the target of the call
argument list. */ expansion as the return slot for a call that returns in memory. */
#define CALL_EXPR_HAS_RETURN_SLOT_ADDR(NODE) ((NODE)->common.private_flag) #define CALL_EXPR_RETURN_SLOT_OPT(NODE) ((NODE)->common.private_flag)
/* In a RESULT_DECL or PARM_DECL, means that it is passed by invisible /* In a RESULT_DECL or PARM_DECL, means that it is passed by invisible
reference (and the TREE_TYPE is a pointer to the true type). */ reference (and the TREE_TYPE is a pointer to the true type). */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment