On Thu, Oct 31, 2024 at 9:08 AM Jan Hubicka <hubi...@ucw.cz> wrote: > > Hi, > this patch lets us to remove > void *mem = __builtin_malloc (s); > if (!mem) > __builtin_abort (); > __builtin_free (mem); > > Where we previously stopped on "if (!mem)" marking __builtin_malloc necessary. > This is done by matching such conditional in tree-ssa-dce.cc and playing same > game > as we do for free functions. We consider if itself necessary (since it does > control flow) but we do not propagate necessity over the SSA name. > > In removal stage we check whether SSA name is needed and if not, we replace > it by constant 1, which makes the conditonal to be folded as constant in > cfgccleanup. > > While working on this, I refactored the code a bit to avoid duplicate matching > of allocation functions which made me notice that we handled strdup/strndup > just > half way and this prevented us from removing it, which is also fixed and > tested > in the testcase. > > Note that testcase pr68078.f90 checks that failed allocation is reported > when memory limits are set. We no longer report it since the memory is > unused and that is why I needed to disable DCE. > > I alos tested that with Jakub's patch we now eliminate new operator with > std::nothrow_t. > > Bootstrapped/regtested x86_64-linux, OK?
Note the request for this optimization is recorded in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19831#c20 so it should have `PR tree-optimization/19831` on the changelog at least. Also how does this change affect the other tests in that comment? Like the alloc_fails/alloc_success one? or the realloc one? Thanks, Andrew > > gcc/ChangeLog: > > * tree-ssa-dce.cc (is_removable_allocation_p): Break out from > ... > (mark_stmt_if_obviously_necessary): ... here; also check that > operator new satisfies gimple_call_from_new_or_delete. > (checks_return_value_of_removable_allocation_p): New Function. > (mark_all_reaching_defs_necessary_1): add missing case ofr > STRDUP and STRNDUP > (propagate_necessity): Use is_removable_allocation_p and > checks_return_value_of_removable_allocation_p. > (eliminate_unnecessary_stmts): Update conditionals that use > removed allocation; use is_removable_allocation_p. > > gcc/testsuite/ChangeLog: > > * g++.dg/cdce3.C: Disable allocation DCE. > * g++.dg/tree-ssa/pr19476-1.C: Likewise. > * g++.dg/tree-ssa/pr19476-2.C: Likewise. > * g++.dg/tree-ssa/pr19476-3.C: Likewise. > * g++.dg/tree-ssa/pr19476-4.C: Likewise. > * gcc.dg/tree-ssa/pr19831-3.c: Test is now fully optiimzed. > * gfortran.dg/pr68078.f90: Disable DCE. > * gcc.dg/tree-ssa/dce-3.c: New test. > > diff --git a/gcc/testsuite/g++.dg/cdce3.C b/gcc/testsuite/g++.dg/cdce3.C > index 2543317f6b2..9aecf58a8b8 100644 > --- a/gcc/testsuite/g++.dg/cdce3.C > +++ b/gcc/testsuite/g++.dg/cdce3.C > @@ -1,6 +1,6 @@ > /* { dg-do run } */ > /* { dg-require-effective-target c99_runtime } */ > -/* { dg-options "-O2 -fmath-errno -fdump-tree-cdce-details -lm" } */ > +/* { dg-options "-O2 -fmath-errno -fdump-tree-cdce-details -lm > -fno-allocation-dce" } */ > /* { dg-additional-options "-DLARGE_LONG_DOUBLE" { target large_long_double > } } */ > /* { dg-additional-options "-DGNU_EXTENSION" { target pow10 } } */ > /* { dg-add-options ieee } */ > diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr19476-1.C > b/gcc/testsuite/g++.dg/tree-ssa/pr19476-1.C > index 25867185648..d17be88642f 100644 > --- a/gcc/testsuite/g++.dg/tree-ssa/pr19476-1.C > +++ b/gcc/testsuite/g++.dg/tree-ssa/pr19476-1.C > @@ -1,5 +1,5 @@ > /* { dg-do compile } */ > -/* { dg-options "-O -fdump-tree-dom2 -fdelete-null-pointer-checks" } */ > +/* { dg-options "-O -fdump-tree-dom2 -fdelete-null-pointer-checks > -fno-allocation-dce" } */ > /* { dg-skip-if "" keeps_null_pointer_checks } */ > > // See pr19476-5.C for a version without including <new>. > diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr19476-2.C > b/gcc/testsuite/g++.dg/tree-ssa/pr19476-2.C > index 0f657d5bd0c..aab7d04ab92 100644 > --- a/gcc/testsuite/g++.dg/tree-ssa/pr19476-2.C > +++ b/gcc/testsuite/g++.dg/tree-ssa/pr19476-2.C > @@ -1,5 +1,5 @@ > /* { dg-do compile } */ > -/* { dg-options "-O2 -fdump-tree-optimized -fdelete-null-pointer-checks" } */ > +/* { dg-options "-O2 -fdump-tree-optimized -fdelete-null-pointer-checks > -fno-allocation-dce" } */ > /* { dg-skip-if "" keeps_null_pointer_checks } */ > > #include <new> > diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr19476-3.C > b/gcc/testsuite/g++.dg/tree-ssa/pr19476-3.C > index fd5117fceb8..0894178c470 100644 > --- a/gcc/testsuite/g++.dg/tree-ssa/pr19476-3.C > +++ b/gcc/testsuite/g++.dg/tree-ssa/pr19476-3.C > @@ -1,5 +1,5 @@ > /* { dg-do compile } */ > -/* { dg-options "-O3 -fcheck-new -fdump-tree-optimized" } */ > +/* { dg-options "-O3 -fcheck-new -fdump-tree-optimized -fno-allocation-dce" > } */ > > #include <new> > > diff --git a/gcc/testsuite/g++.dg/tree-ssa/pr19476-4.C > b/gcc/testsuite/g++.dg/tree-ssa/pr19476-4.C > index 5f137391cc6..e5c216a64fa 100644 > --- a/gcc/testsuite/g++.dg/tree-ssa/pr19476-4.C > +++ b/gcc/testsuite/g++.dg/tree-ssa/pr19476-4.C > @@ -1,5 +1,5 @@ > /* { dg-do compile } */ > -/* { dg-options "-O3 -fno-delete-null-pointer-checks -fdump-tree-optimized" > } */ > +/* { dg-options "-O3 -fno-delete-null-pointer-checks -fdump-tree-optimized > -fno-allocation-dce" } */ > > #include <new> > > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/dce-3.c > b/gcc/testsuite/gcc.dg/tree-ssa/dce-3.c > new file mode 100644 > index 00000000000..5b53e8f9767 > --- /dev/null > +++ b/gcc/testsuite/gcc.dg/tree-ssa/dce-3.c > @@ -0,0 +1,34 @@ > +/* { dg-do compile } */ > +/* { dg-options "-O2 -fdump-tree-cddce1" } */ > +void > +test (int s) > +{ > + int *mem = (int *) __builtin_malloc (s * sizeof (int)); > + if (mem) > + { > + for (int i = 0; i < s; i++) > + mem[i] = i; > + __builtin_free (mem); > + } > +} > + > +void > +test2 (int s) > +{ > + void *mem = __builtin_malloc (s); > + if (!mem) > + __builtin_abort (); > + __builtin_free (mem); > +} > +void > +test3 (char *s) > +{ > + char *c = __builtin_strdup (s); > + if (!c) > + __builtin_abort (); > + __builtin_free (c); > +} > +/* { dg-final { scan-tree-dump-not "if" "cddce1"} } */ > +/* { dg-final { scan-tree-dump-not "alloc" "cddce1"} } */ > +/* { dg-final { scan-tree-dump-not "free" "cddce1"} } */ > +/* { dg-final { scan-tree-dump-not "strdup" "cddce1"} } */ > diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr19831-3.c > b/gcc/testsuite/gcc.dg/tree-ssa/pr19831-3.c > index f5cb72daa33..e6ae6582ab2 100644 > --- a/gcc/testsuite/gcc.dg/tree-ssa/pr19831-3.c > +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr19831-3.c > @@ -29,10 +29,5 @@ void test6(void) > Assume p was non-NULL for test2. > For test5, it doesn't matter if p is NULL or non-NULL. */ > > -/* { dg-final { scan-tree-dump-times "free" 0 "optimized" { xfail *-*-* } } > } */ > -/* { dg-final { scan-tree-dump-times "malloc" 0 "optimized" { xfail *-*-* } > } } */ > - > -/* But make sure we don't partially optimize for now. */ > - > -/* { dg-final { scan-tree-dump-times "free" 3 "optimized" } } */ > -/* { dg-final { scan-tree-dump-times "malloc" 3 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times "free" 0 "optimized" } } */ > +/* { dg-final { scan-tree-dump-times "malloc" 0 "optimized" } } */ > diff --git a/gcc/testsuite/gfortran.dg/pr68078.f90 > b/gcc/testsuite/gfortran.dg/pr68078.f90 > index ebe26d55d2b..b71b0fa4e33 100644 > --- a/gcc/testsuite/gfortran.dg/pr68078.f90 > +++ b/gcc/testsuite/gfortran.dg/pr68078.f90 > @@ -1,4 +1,6 @@ > ! { dg-do run { target i?86-*-linux* x86_64-*-linux* } } > +! disable DCE so the allocations are not optimized away > +! { dg-additional-options "-fno-tree-dce" } > ! { dg-additional-sources set_vm_limit.c } > ! > ! This test calls set_vm_limit to set an artificially low address space > diff --git a/gcc/tree-ssa-dce.cc b/gcc/tree-ssa-dce.cc > index c44eff35b84..aa77c7367bf 100644 > --- a/gcc/tree-ssa-dce.cc > +++ b/gcc/tree-ssa-dce.cc > @@ -240,6 +240,55 @@ mark_operand_necessary (tree op) > worklist.safe_push (stmt); > } > > +/* Return true if STMT is a call to allocation function that can be > + optimized out if the memory block is never used and only freed. */ > + > +static bool > +is_removable_allocation_p (gcall *stmt) > +{ > + tree callee = gimple_call_fndecl (stmt); > + if (callee != NULL_TREE > + && fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > + switch (DECL_FUNCTION_CODE (callee)) > + { > + case BUILT_IN_MALLOC: > + case BUILT_IN_ALIGNED_ALLOC: > + case BUILT_IN_CALLOC: > + CASE_BUILT_IN_ALLOCA: > + case BUILT_IN_STRDUP: > + case BUILT_IN_STRNDUP: > + case BUILT_IN_GOMP_ALLOC: > + return true; > + > + default:; > + } > + > + if (callee != NULL_TREE > + && flag_allocation_dce > + && gimple_call_from_new_or_delete (stmt) > + && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (callee)) > + return true; > + return false; > +} > + > +/* Return true if STMT is a conditional > + if (ptr != NULL) > + where ptr was returned by a removable allocation function. */ > + > +static bool > +checks_return_value_of_removable_allocation_p (gimple *stmt) > +{ > + gcall *def_stmt; > + return gimple_code (stmt) == GIMPLE_COND > + && (gimple_cond_code (stmt) == EQ_EXPR > + || gimple_cond_code (stmt) == NE_EXPR) > + && integer_zerop (gimple_cond_rhs (stmt)) > + && TREE_CODE (gimple_cond_lhs (stmt)) == SSA_NAME > + && (def_stmt = dyn_cast <gcall *> > + (SSA_NAME_DEF_STMT (gimple_cond_lhs (stmt)))) > + && is_removable_allocation_p (def_stmt); > +} > + > > /* Mark STMT as necessary if it obviously is. Add it to the worklist if > it can make other statements necessary. > @@ -271,38 +320,23 @@ mark_stmt_if_obviously_necessary (gimple *stmt, bool > aggressive) > > case GIMPLE_CALL: > { > + gcall *call = as_a <gcall *> (stmt); > + > /* Never elide a noreturn call we pruned control-flow for. */ > - if ((gimple_call_flags (stmt) & ECF_NORETURN) > - && gimple_call_ctrl_altering_p (stmt)) > + if ((gimple_call_flags (call) & ECF_NORETURN) > + && gimple_call_ctrl_altering_p (call)) > { > - mark_stmt_necessary (stmt, true); > + mark_stmt_necessary (call, true); > return; > } > > - tree callee = gimple_call_fndecl (stmt); > - if (callee != NULL_TREE > - && fndecl_built_in_p (callee, BUILT_IN_NORMAL)) > - switch (DECL_FUNCTION_CODE (callee)) > - { > - case BUILT_IN_MALLOC: > - case BUILT_IN_ALIGNED_ALLOC: > - case BUILT_IN_CALLOC: > - CASE_BUILT_IN_ALLOCA: > - case BUILT_IN_STRDUP: > - case BUILT_IN_STRNDUP: > - case BUILT_IN_GOMP_ALLOC: > - return; > - > - default:; > - } > > - if (callee != NULL_TREE > - && flag_allocation_dce > - && DECL_IS_REPLACEABLE_OPERATOR_NEW_P (callee)) > + if (is_removable_allocation_p (call)) > return; > > + > /* For __cxa_atexit calls, don't mark as necessary right away. */ > - if (is_removable_cxa_atexit_call (stmt)) > + if (is_removable_cxa_atexit_call (call)) > return; > > /* IFN_GOACC_LOOP calls are necessary in that they are used to > @@ -311,9 +345,9 @@ mark_stmt_if_obviously_necessary (gimple *stmt, bool > aggressive) > survive from aggressive loop removal for it has loop exit and > is assumed to be finite. Therefore, we need to explicitly mark > these calls. (An example is libgomp.oacc-c-c++-common/pr84955.c) */ > - if (gimple_call_internal_p (stmt, IFN_GOACC_LOOP)) > + if (gimple_call_internal_p (call, IFN_GOACC_LOOP)) > { > - mark_stmt_necessary (stmt, true); > + mark_stmt_necessary (call, true); > return; > } > break; > @@ -667,6 +701,8 @@ mark_all_reaching_defs_necessary_1 (ao_ref *ref > ATTRIBUTE_UNUSED, > case BUILT_IN_ALIGNED_ALLOC: > case BUILT_IN_CALLOC: > CASE_BUILT_IN_ALLOCA: > + case BUILT_IN_STRDUP: > + case BUILT_IN_STRNDUP: > case BUILT_IN_FREE: > case BUILT_IN_GOMP_ALLOC: > case BUILT_IN_GOMP_FREE: > @@ -891,19 +927,11 @@ propagate_necessity (bool aggressive) > { > tree ptr = gimple_call_arg (stmt, 0); > gcall *def_stmt; > - tree def_callee; > /* If the pointer we free is defined by an allocation > function do not add the call to the worklist. */ > if (TREE_CODE (ptr) == SSA_NAME > && (def_stmt = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (ptr))) > - && (def_callee = gimple_call_fndecl (def_stmt)) > - && ((DECL_BUILT_IN_CLASS (def_callee) == BUILT_IN_NORMAL > - && (DECL_FUNCTION_CODE (def_callee) == > BUILT_IN_ALIGNED_ALLOC > - || DECL_FUNCTION_CODE (def_callee) == > BUILT_IN_MALLOC > - || DECL_FUNCTION_CODE (def_callee) == > BUILT_IN_CALLOC > - || DECL_FUNCTION_CODE (def_callee) == > BUILT_IN_GOMP_ALLOC)) > - || (DECL_IS_REPLACEABLE_OPERATOR_NEW_P (def_callee) > - && gimple_call_from_new_or_delete (def_stmt)))) > + && is_removable_allocation_p (def_stmt)) > { > if (is_delete_operator > && !valid_new_delete_pair_p (def_stmt, stmt)) > @@ -925,6 +953,9 @@ propagate_necessity (bool aggressive) > } > } > > + if (checks_return_value_of_removable_allocation_p (stmt)) > + continue; > + > FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE) > mark_operand_necessary (use); > > @@ -1379,7 +1410,6 @@ eliminate_unnecessary_stmts (bool aggressive) > basic_block bb; > gimple_stmt_iterator gsi, psi; > gimple *stmt; > - tree call; > auto_vec<edge> to_remove_edges; > > if (dump_file && (dump_flags & TDF_DETAILS)) > @@ -1448,6 +1478,23 @@ eliminate_unnecessary_stmts (bool aggressive) > gimple_set_plf (stmt, STMT_NECESSARY, false); > } > } > + /* Conditional checking that return value of allocation is non-NULL > + can be turned to constant if the allocation itself > + is unnecesary. */ > + if (gimple_plf (stmt, STMT_NECESSARY) > + && gimple_code (stmt) == GIMPLE_COND > + && TREE_CODE (gimple_cond_lhs (stmt)) == SSA_NAME) > + { > + gimple *def_stmt = SSA_NAME_DEF_STMT (gimple_cond_lhs (stmt)); > + if (!gimple_nop_p (def_stmt) > + && !gimple_plf (def_stmt, STMT_NECESSARY)) > + { > + gcc_checking_assert > + (checks_return_value_of_removable_allocation_p > (stmt)); > + gimple_cond_set_lhs (as_a <gcond *>(stmt), > integer_one_node); > + update_stmt (stmt); > + } > + } > > /* If GSI is not necessary then remove it. */ > if (!gimple_plf (stmt, STMT_NECESSARY)) > @@ -1482,11 +1529,11 @@ eliminate_unnecessary_stmts (bool aggressive) > remove_dead_stmt (&gsi, bb, to_remove_edges); > continue; > } > - else if (is_gimple_call (stmt)) > + else if (gcall *call_stmt = dyn_cast <gcall *> (stmt)) > { > - tree name = gimple_call_lhs (stmt); > + tree name = gimple_call_lhs (call_stmt); > > - notice_special_calls (as_a <gcall *> (stmt)); > + notice_special_calls (call_stmt); > > /* When LHS of var = call (); is dead, simplify it into > call (); saving one operand. */ > @@ -1496,36 +1543,30 @@ eliminate_unnecessary_stmts (bool aggressive) > /* Avoid doing so for allocation calls which we > did not mark as necessary, it will confuse the > special logic we apply to malloc/free pair removal. */ > - && (!(call = gimple_call_fndecl (stmt)) > - || ((DECL_BUILT_IN_CLASS (call) != BUILT_IN_NORMAL > - || (DECL_FUNCTION_CODE (call) != > BUILT_IN_ALIGNED_ALLOC > - && DECL_FUNCTION_CODE (call) != BUILT_IN_MALLOC > - && DECL_FUNCTION_CODE (call) != BUILT_IN_CALLOC > - && !ALLOCA_FUNCTION_CODE_P > - (DECL_FUNCTION_CODE (call)))) > - && !DECL_IS_REPLACEABLE_OPERATOR_NEW_P (call)))) > + && !is_removable_allocation_p (call_stmt)) > { > something_changed = true; > if (dump_file && (dump_flags & TDF_DETAILS)) > { > fprintf (dump_file, "Deleting LHS of call: "); > - print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM); > + print_gimple_stmt (dump_file, call_stmt, 0, TDF_SLIM); > fprintf (dump_file, "\n"); > } > > - gimple_call_set_lhs (stmt, NULL_TREE); > - maybe_clean_or_replace_eh_stmt (stmt, stmt); > - update_stmt (stmt); > + gimple_call_set_lhs (call_stmt, NULL_TREE); > + maybe_clean_or_replace_eh_stmt (call_stmt, call_stmt); > + update_stmt (call_stmt); > release_ssa_name (name); > > /* GOMP_SIMD_LANE (unless three argument) or ASAN_POISON > without lhs is not needed. */ > - if (gimple_call_internal_p (stmt)) > - switch (gimple_call_internal_fn (stmt)) > + if (gimple_call_internal_p (call_stmt)) > + switch (gimple_call_internal_fn (call_stmt)) > { > case IFN_GOMP_SIMD_LANE: > - if (gimple_call_num_args (stmt) >= 3 > - && !integer_nonzerop (gimple_call_arg (stmt, 2))) > + if (gimple_call_num_args (call_stmt) >= 3 > + && !integer_nonzerop > + (gimple_call_arg (call_stmt, 2))) > break; > /* FALLTHRU */ > case IFN_ASAN_POISON: > @@ -1535,8 +1576,8 @@ eliminate_unnecessary_stmts (bool aggressive) > break; > } > } > - else if (gimple_call_internal_p (stmt)) > - switch (gimple_call_internal_fn (stmt)) > + else if (gimple_call_internal_p (call_stmt)) > + switch (gimple_call_internal_fn (call_stmt)) > { > case IFN_ADD_OVERFLOW: > maybe_optimize_arith_overflow (&gsi, PLUS_EXPR); > @@ -1548,11 +1589,11 @@ eliminate_unnecessary_stmts (bool aggressive) > maybe_optimize_arith_overflow (&gsi, MULT_EXPR); > break; > case IFN_UADDC: > - if (integer_zerop (gimple_call_arg (stmt, 2))) > + if (integer_zerop (gimple_call_arg (call_stmt, 2))) > maybe_optimize_arith_overflow (&gsi, PLUS_EXPR); > break; > case IFN_USUBC: > - if (integer_zerop (gimple_call_arg (stmt, 2))) > + if (integer_zerop (gimple_call_arg (call_stmt, 2))) > maybe_optimize_arith_overflow (&gsi, MINUS_EXPR); > break; > default: