aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/ChangeLog11
-rw-r--r--gcc/cp/ChangeLog7
-rw-r--r--gcc/cp/decl.c109
-rw-r--r--gcc/passes.c1
-rw-r--r--gcc/testsuite/g++.dg/eh/init-temp1.C44
-rw-r--r--gcc/tree-eh.c156
-rw-r--r--gcc/tree-pass.h1
7 files changed, 296 insertions, 33 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 02f3040..73010ea 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,14 @@
+2007-10-03 Jason Merrill <jason@redhat.com>
+
+ PR c++/15764
+ * tree-eh.c (same_handler_p): New fn.
+ (optimize_double_finally): New fn.
+ (refactor_eh_r): New fn.
+ (refactor_eh): New fn.
+ (pass_refactor_eh): New pass.
+ * tree-pass.h: Declare it.
+ * passes.c (init_optimization_passes): Add it.
+
2007-10-03 Doug Kwan <dougkwan@google.com>
Richard Guenther <rguenther@suse.de>
diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog
index 5609f1a..02d549f 100644
--- a/gcc/cp/ChangeLog
+++ b/gcc/cp/ChangeLog
@@ -1,3 +1,10 @@
+2007-10-03 Jason Merrill <jason@redhat.com>
+
+ PR c++/15764
+ * decl.c (wrap_cleanups_r): New fn.
+ (wrap_temporary_cleanups): New fn.
+ (initialize_local_var): Call it.
+
2007-09-29 Jason Merrill <jason@redhat.com>
PR c++/33094
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 469e6b8..d653fc4 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -5136,6 +5136,41 @@ make_rtl_for_nonlocal_decl (tree decl, tree init, const char* asmspec)
rest_of_decl_compilation (decl, toplev, at_eof);
}
+/* walk_tree helper for wrap_temporary_cleanups, below. */
+
+static tree
+wrap_cleanups_r (tree *stmt_p, int *walk_subtrees, void *data)
+{
+ if (TYPE_P (*stmt_p))
+ {
+ *walk_subtrees = 0;
+ return NULL_TREE;
+ }
+
+ if (TREE_CODE (*stmt_p) == TARGET_EXPR)
+ {
+ tree guard = (tree)data;
+ tree tcleanup = TARGET_EXPR_CLEANUP (*stmt_p);
+
+ tcleanup = build2 (TRY_CATCH_EXPR, void_type_node, tcleanup, guard);
+
+ TARGET_EXPR_CLEANUP (*stmt_p) = tcleanup;
+ }
+
+ return NULL_TREE;
+}
+
+/* We're initializing a local variable which has a cleanup GUARD. If there
+ are any temporaries used in the initializer INIT of this variable, we
+ need to wrap their cleanups with TRY_CATCH_EXPR (, GUARD) so that the
+ variable will be cleaned up properly if one of them throws. */
+
+static void
+wrap_temporary_cleanups (tree init, tree guard)
+{
+ cp_walk_tree_without_duplicates (&init, wrap_cleanups_r, (void *)guard);
+}
+
/* Generate code to initialize DECL (a local variable). */
static void
@@ -5143,6 +5178,7 @@ initialize_local_var (tree decl, tree init)
{
tree type = TREE_TYPE (decl);
tree cleanup;
+ int already_used;
gcc_assert (TREE_CODE (decl) == VAR_DECL
|| TREE_CODE (decl) == RESULT_DECL);
@@ -5153,46 +5189,53 @@ initialize_local_var (tree decl, tree init)
/* If we used it already as memory, it must stay in memory. */
DECL_INITIAL (decl) = NULL_TREE;
TREE_ADDRESSABLE (decl) = TREE_USED (decl);
+ return;
}
- if (DECL_SIZE (decl) && type != error_mark_node)
- {
- int already_used;
+ if (type == error_mark_node)
+ return;
- /* Compute and store the initial value. */
- already_used = TREE_USED (decl) || TREE_USED (type);
+ /* Compute and store the initial value. */
+ already_used = TREE_USED (decl) || TREE_USED (type);
- /* Perform the initialization. */
- if (init)
- {
- int saved_stmts_are_full_exprs_p;
+ /* Generate a cleanup, if necessary. */
+ cleanup = cxx_maybe_build_cleanup (decl);
- gcc_assert (building_stmt_tree ());
- saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
- current_stmt_tree ()->stmts_are_full_exprs_p = 1;
- finish_expr_stmt (init);
- current_stmt_tree ()->stmts_are_full_exprs_p =
- saved_stmts_are_full_exprs_p;
- }
+ /* Perform the initialization. */
+ if (init)
+ {
+ int saved_stmts_are_full_exprs_p;
- /* Set this to 0 so we can tell whether an aggregate which was
- initialized was ever used. Don't do this if it has a
- destructor, so we don't complain about the 'resource
- allocation is initialization' idiom. Now set
- attribute((unused)) on types so decls of that type will be
- marked used. (see TREE_USED, above.) */
- if (TYPE_NEEDS_CONSTRUCTING (type)
- && ! already_used
- && TYPE_HAS_TRIVIAL_DESTRUCTOR (type)
- && DECL_NAME (decl))
- TREE_USED (decl) = 0;
- else if (already_used)
- TREE_USED (decl) = 1;
- }
+ /* If we're only initializing a single object, guard the destructors
+ of any temporaries used in its initializer with its destructor.
+ This isn't right for arrays because each element initialization is
+ a full-expression. */
+ if (cleanup && TREE_CODE (type) != ARRAY_TYPE)
+ wrap_temporary_cleanups (init, cleanup);
- /* Generate a cleanup, if necessary. */
- cleanup = cxx_maybe_build_cleanup (decl);
- if (DECL_SIZE (decl) && cleanup)
+ gcc_assert (building_stmt_tree ());
+ saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
+ current_stmt_tree ()->stmts_are_full_exprs_p = 1;
+ finish_expr_stmt (init);
+ current_stmt_tree ()->stmts_are_full_exprs_p =
+ saved_stmts_are_full_exprs_p;
+ }
+
+ /* Set this to 0 so we can tell whether an aggregate which was
+ initialized was ever used. Don't do this if it has a
+ destructor, so we don't complain about the 'resource
+ allocation is initialization' idiom. Now set
+ attribute((unused)) on types so decls of that type will be
+ marked used. (see TREE_USED, above.) */
+ if (TYPE_NEEDS_CONSTRUCTING (type)
+ && ! already_used
+ && TYPE_HAS_TRIVIAL_DESTRUCTOR (type)
+ && DECL_NAME (decl))
+ TREE_USED (decl) = 0;
+ else if (already_used)
+ TREE_USED (decl) = 1;
+
+ if (cleanup)
finish_decl_cleanup (decl, cleanup);
}
diff --git a/gcc/passes.c b/gcc/passes.c
index 74f48a8..25644bc 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -480,6 +480,7 @@ init_optimization_passes (void)
NEXT_PASS (pass_mudflap_1);
NEXT_PASS (pass_lower_omp);
NEXT_PASS (pass_lower_cf);
+ NEXT_PASS (pass_refactor_eh);
NEXT_PASS (pass_lower_eh);
NEXT_PASS (pass_build_cfg);
NEXT_PASS (pass_lower_complex_O0);
diff --git a/gcc/testsuite/g++.dg/eh/init-temp1.C b/gcc/testsuite/g++.dg/eh/init-temp1.C
new file mode 100644
index 0000000..29eae69
--- /dev/null
+++ b/gcc/testsuite/g++.dg/eh/init-temp1.C
@@ -0,0 +1,44 @@
+// PR c++/15764
+
+extern "C" void abort ();
+
+int counter = 0;
+int thrown;
+struct a {
+ ~a () { if (thrown++ == 0) throw 42; }
+};
+
+int f (a const&) { return 1; }
+int f (a const&, a const&) { return 1; }
+
+struct b {
+ b (...) { ++counter; }
+ ~b () { --counter; }
+};
+
+bool p;
+void g()
+{
+ if (p) throw 42;
+}
+
+int main () {
+ thrown = 0;
+ try {
+ b tmp(f (a(), a()));
+
+ g();
+ }
+ catch (...) {}
+
+ thrown = 0;
+ try {
+ b tmp(f (a()));
+
+ g();
+ }
+ catch (...) {}
+
+ if (counter != 0)
+ abort ();
+}
diff --git a/gcc/tree-eh.c b/gcc/tree-eh.c
index f8aed14..3ea582f 100644
--- a/gcc/tree-eh.c
+++ b/gcc/tree-eh.c
@@ -2093,3 +2093,159 @@ maybe_clean_or_replace_eh_stmt (tree old_stmt, tree new_stmt)
return false;
}
+
+/* Returns TRUE if oneh and twoh are exception handlers (op 1 of
+ TRY_CATCH_EXPR or TRY_FINALLY_EXPR that are similar enough to be
+ considered the same. Currently this only handles handlers consisting of
+ a single call, as that's the important case for C++: a destructor call
+ for a particular object showing up in multiple handlers. */
+
+static bool
+same_handler_p (tree oneh, tree twoh)
+{
+ tree_stmt_iterator i;
+ tree ones, twos;
+ int ai;
+
+ i = tsi_start (oneh);
+ if (!tsi_one_before_end_p (i))
+ return false;
+ ones = tsi_stmt (i);
+
+ i = tsi_start (twoh);
+ if (!tsi_one_before_end_p (i))
+ return false;
+ twos = tsi_stmt (i);
+
+ if (TREE_CODE (ones) != CALL_EXPR
+ || TREE_CODE (twos) != CALL_EXPR
+ || !operand_equal_p (CALL_EXPR_FN (ones), CALL_EXPR_FN (twos), 0)
+ || call_expr_nargs (ones) != call_expr_nargs (twos))
+ return false;
+
+ for (ai = 0; ai < call_expr_nargs (ones); ++ai)
+ if (!operand_equal_p (CALL_EXPR_ARG (ones, ai),
+ CALL_EXPR_ARG (twos, ai), 0))
+ return false;
+
+ return true;
+}
+
+/* Optimize
+ try { A() } finally { try { ~B() } catch { ~A() } }
+ try { ... } finally { ~A() }
+ into
+ try { A() } catch { ~B() }
+ try { ~B() ... } finally { ~A() }
+
+ This occurs frequently in C++, where A is a local variable and B is a
+ temporary used in the initializer for A. */
+
+static void
+optimize_double_finally (tree one, tree two)
+{
+ tree oneh;
+ tree_stmt_iterator i;
+
+ i = tsi_start (TREE_OPERAND (one, 1));
+ if (!tsi_one_before_end_p (i))
+ return;
+
+ oneh = tsi_stmt (i);
+ if (TREE_CODE (oneh) != TRY_CATCH_EXPR)
+ return;
+
+ if (same_handler_p (TREE_OPERAND (oneh, 1), TREE_OPERAND (two, 1)))
+ {
+ tree twoh;
+
+ tree b = TREE_OPERAND (oneh, 0);
+ TREE_OPERAND (one, 1) = b;
+ TREE_SET_CODE (one, TRY_CATCH_EXPR);
+
+ b = tsi_stmt (tsi_start (b));
+ twoh = TREE_OPERAND (two, 0);
+ /* same_handler_p only handles single-statement handlers,
+ so there must only be one statement. */
+ i = tsi_start (twoh);
+ tsi_link_before (&i, unshare_expr (b), TSI_SAME_STMT);
+ }
+}
+
+/* Perform EH refactoring optimizations that are simpler to do when code
+ flow has been lowered but EH structurs haven't. */
+
+static void
+refactor_eh_r (tree t)
+{
+ tailrecurse:
+ switch (TREE_CODE (t))
+ {
+ case TRY_FINALLY_EXPR:
+ case TRY_CATCH_EXPR:
+ refactor_eh_r (TREE_OPERAND (t, 0));
+ t = TREE_OPERAND (t, 1);
+ goto tailrecurse;
+
+ case CATCH_EXPR:
+ t = CATCH_BODY (t);
+ goto tailrecurse;
+
+ case EH_FILTER_EXPR:
+ t = EH_FILTER_FAILURE (t);
+ goto tailrecurse;
+
+ case STATEMENT_LIST:
+ {
+ tree_stmt_iterator i;
+ tree one = NULL_TREE, two = NULL_TREE;
+ /* Try to refactor double try/finally. */
+ for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i))
+ {
+ one = two;
+ two = tsi_stmt (i);
+ if (one && two
+ && TREE_CODE (one) == TRY_FINALLY_EXPR
+ && TREE_CODE (two) == TRY_FINALLY_EXPR)
+ optimize_double_finally (one, two);
+ if (one)
+ refactor_eh_r (one);
+ }
+ if (two)
+ {
+ t = two;
+ goto tailrecurse;
+ }
+ }
+ break;
+
+ default:
+ /* A type, a decl, or some kind of statement that we're not
+ interested in. Don't walk them. */
+ break;
+ }
+}
+
+static unsigned
+refactor_eh (void)
+{
+ refactor_eh_r (DECL_SAVED_TREE (current_function_decl));
+ return 0;
+}
+
+struct tree_opt_pass pass_refactor_eh =
+{
+ "ehopt", /* name */
+ NULL, /* gate */
+ refactor_eh, /* execute */
+ NULL, /* sub */
+ NULL, /* next */
+ 0, /* static_pass_number */
+ TV_TREE_EH, /* tv_id */
+ PROP_gimple_lcf, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ TODO_dump_func, /* todo_flags_finish */
+ 0 /* letter */
+};
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index b19d3d0..1f748e8 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -246,6 +246,7 @@ extern struct tree_opt_pass pass_mudflap_1;
extern struct tree_opt_pass pass_mudflap_2;
extern struct tree_opt_pass pass_remove_useless_stmts;
extern struct tree_opt_pass pass_lower_cf;
+extern struct tree_opt_pass pass_refactor_eh;
extern struct tree_opt_pass pass_lower_eh;
extern struct tree_opt_pass pass_build_cfg;
extern struct tree_opt_pass pass_tree_profile;