https://gcc.gnu.org/g:a5fabe7e20f4647bdfff5ed9d11d8526b11dae30
commit r17-5728-ga5fabe7e20f4647bdfff5ed9d11d8526b11dae30 Author: Ondřej Machota <[email protected]> Date: Wed Oct 28 22:04:43 2026 +0100 tree-optimization: Remove unused allocations whose result is compared with NULL Hi, Thanks a lot for the review. Here are the changes in V2: - checks_return_value_of_removable_allocation_p has been rewritten. It now uses dyn_cast to gassign/gcond, and the two cases are combined. - The assignment case uses constant_boolean_node and gimple_assign_set_rhs_with_ops instead of gimple_assign_set_rhs_from_tree. Is there anything else that needs fixing or improvement? Could someone push this patch once it is OK for trunk? I don’t have write access. Bootstrapped and regression tested on x86_64-pc-linux-gnu with no regressions. Best regards, Ondřej Machota -- 8< -- Jan Hubicka's earlier change ("Remove allocations which are used only for NULL pointer check and free", 7828dc0) taught tree-ssa-dce to remove an allocation/deallocation pair when the returned pointer is only used by a conditional comparing it against NULL. It does not handle the case where the result of the comparison is used in an assignment. With -O2 -fno-exceptions: int x (std::size_t n) { auto ptr = new (std::nothrow) int[n]; delete[] ptr; return ptr != 0; } int y (std::size_t n) { auto ptr = new (std::nothrow) int[n]; delete[] ptr return ptr != 0 ? 1 : 0; } y is optimized to "return 1", but x still calls operator new[] and operator delete[]. Treat assignments of "ptr == NULL" and "ptr != NULL" like the conditional form: they no longer keep the allocation alive and are folded to a constant if the allocation is removed. Additionally, fix a typo in -fmalloc-dce=, which was incorrectly setting flag_malloc_dse instead of flag_malloc_dce, and include two new testcases to verify the flag's behavior. gcc/ChangeLog: * common.opt (fmalloc-dce): Use flag_malloc_dce instead of flag_malloc_dse. * tree-ssa-dce.cc (checks_return_value_of_removable_allocation_p): Also handle comparisons against NULL in assignments. (eliminate_unnecessary_stmts): Fold such comparisons to a constant when the allocation is removed. gcc/testsuite/ChangeLog: * g++.dg/tree-ssa/dce-2.C: New test. * g++.dg/tree-ssa/dce-3.C: New test. * gcc.dg/tree-ssa/malloc-dce-1.c: New test. * gcc.dg/tree-ssa/malloc-dce-2.c: New test. Signed-off-by: Ondřej Machota <[email protected]> Diff: --- gcc/common.opt | 2 +- gcc/testsuite/g++.dg/tree-ssa/dce-2.C | 59 +++++++++++++++++++++++++ gcc/testsuite/g++.dg/tree-ssa/dce-3.C | 43 ++++++++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-1.c | 29 +++++++++++++ gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-2.c | 29 +++++++++++++ gcc/tree-ssa-dce.cc | 65 +++++++++++++++++++++++----- 6 files changed, 216 insertions(+), 11 deletions(-) diff --git a/gcc/common.opt b/gcc/common.opt index 69a55b8d40d6..fb21a0c17350 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -2401,7 +2401,7 @@ Common Var(flag_malloc_dce,2) Init(2) Optimization Allow removal of malloc and free pairs when allocated block is unused. fmalloc-dce= -Common Joined RejectNegative UInteger Var(flag_malloc_dse) Optimization IntegerRange(0, 2) +Common Joined RejectNegative UInteger Var(flag_malloc_dce) Optimization IntegerRange(0, 2) fmem-report Common Var(mem_report) diff --git a/gcc/testsuite/g++.dg/tree-ssa/dce-2.C b/gcc/testsuite/g++.dg/tree-ssa/dce-2.C new file mode 100644 index 000000000000..6a072e0934cf --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/dce-2.C @@ -0,0 +1,59 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized" } */ +#include <new> + +bool ne (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + delete[] p; + return p != 0; +} + +bool eq (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + delete[] p; + return p == 0; +} + +bool conv (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + delete[] p; + return p; +} + +int ternary (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + delete[] p; + return p != 0 ? 1 : 0; +} + +int copy (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + int c = p != 0; + delete[] p; + return c; +} + +int result; +void store (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + result = p != 0; + delete[] p; +} + +int twice (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + int a = p != 0; + int b = p == 0; + delete[] p; + return a + b; +} + +/* { dg-final { scan-tree-dump-not "operator new" "optimized" } } */ +/* { dg-final { scan-tree-dump-not "operator delete" "optimized" } } */ diff --git a/gcc/testsuite/g++.dg/tree-ssa/dce-3.C b/gcc/testsuite/g++.dg/tree-ssa/dce-3.C new file mode 100644 index 000000000000..4a4bf9272a2e --- /dev/null +++ b/gcc/testsuite/g++.dg/tree-ssa/dce-3.C @@ -0,0 +1,43 @@ +/* Every allocation here must survive DCE. */ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-optimized -Wno-mismatched-new-delete" } */ +#include <new> + +bool cmp_other (unsigned s, int *q) +{ + int *p = new (std::nothrow) int[s]; + delete[] p; + return p == q; +} + +int *sink; +void escape (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + sink = p; + delete[] p; +} + +void vol (unsigned s) +{ + char *p = new (std::nothrow) char[s + 1]; + *(volatile char *) p = 4; + delete[] p; +} + +bool direct (unsigned s) +{ + void *p = ::operator new (s, std::nothrow); + ::operator delete (p, std::nothrow); + return p != 0; +} + +/* Mismatched new[] / delete. */ +bool mismatch (unsigned s) +{ + int *p = new (std::nothrow) int[s]; + delete p; + return p != 0; +} + +/* { dg-final { scan-tree-dump-times "operator new" 5 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-1.c b/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-1.c new file mode 100644 index 000000000000..9f56f9af7443 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-1.c @@ -0,0 +1,29 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fmalloc-dce=1 -fdump-tree-optimized" } */ +#include <stdlib.h> + +void +pair_only (size_t n) +{ + free (malloc (n)); /* removed */ +} + +int +ne (size_t n) +{ + void *p = malloc (n); + int r = p != NULL; + free (p); + return r; /* malloc must stay */ +} + +int +eq (size_t n) +{ + void *p = malloc (n); + int r = p == NULL; + free (p); + return r; /* malloc must stay */ +} + +/* { dg-final { scan-tree-dump-times "malloc \\(" 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-2.c b/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-2.c new file mode 100644 index 000000000000..f5e91bb8884f --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/malloc-dce-2.c @@ -0,0 +1,29 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fmalloc-dce=0 -fdump-tree-optimized" } */ +#include <stdlib.h> + +void +pair_only (size_t n) +{ + free (malloc (n)); /* malloc must stay */ +} + +int +ne (size_t n) +{ + void *p = malloc (n); + int r = p != NULL; + free (p); + return r; /* malloc must stay */ +} + +int +eq (size_t n) +{ + void *p = malloc (n); + int r = p == NULL; + free (p); + return r; /* malloc must stay */ +} + +/* { dg-final { scan-tree-dump-times "malloc \\(" 3 "optimized" } } */ diff --git a/gcc/tree-ssa-dce.cc b/gcc/tree-ssa-dce.cc index a5391290c4dc..9d5dce20d03c 100644 --- a/gcc/tree-ssa-dce.cc +++ b/gcc/tree-ssa-dce.cc @@ -341,22 +341,43 @@ is_removable_allocation_p (gcall *stmt, bool non_null_check) } } -/* Return true if STMT is a conditional +/* Return true if STMT is an assignment + foo = (ptr != NULL) + foo = (ptr == NULL) + or a conditional if (ptr != NULL) + 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, true); + enum tree_code code; + tree ptr, value; + + if (gassign *assign = dyn_cast <gassign *> (stmt)) + { + code = gimple_assign_rhs_code (assign); + ptr = gimple_assign_rhs1 (assign); + value = gimple_assign_rhs2 (assign); + } + else if (gcond *cond = dyn_cast <gcond *> (stmt)) + { + code = gimple_cond_code (cond); + ptr = gimple_cond_lhs (cond); + value = gimple_cond_rhs (cond); + } + else + return false; + + if (code != EQ_EXPR && code != NE_EXPR) + return false; + + if (!integer_zerop (value) || TREE_CODE (ptr) != SSA_NAME) + return false; + + gcall *def_stmt = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (ptr)); + return def_stmt && is_removable_allocation_p (def_stmt, true); } @@ -1624,6 +1645,30 @@ eliminate_unnecessary_stmts (bool aggressive) gimple_set_plf (stmt, STMT_NECESSARY, false); } } + + /* Comparisons of the return value of an allocation against NULL + can be turned to a constant if the allocation itself is + unnecessary. */ + if (gimple_plf (stmt, STMT_NECESSARY) && is_gimple_assign (stmt) + && (gimple_assign_rhs_code (stmt) == EQ_EXPR + || gimple_assign_rhs_code (stmt) == NE_EXPR) + && TREE_CODE (gimple_assign_rhs1 (stmt)) == SSA_NAME) + { + tree rhs1 = gimple_assign_rhs1 (stmt); + gimple *def_stmt = SSA_NAME_DEF_STMT (rhs1); + if (!gimple_nop_p (def_stmt) + && !gimple_plf (def_stmt, STMT_NECESSARY)) + { + gcc_checking_assert + (checks_return_value_of_removable_allocation_p (stmt)); + tree lhs = gimple_assign_lhs (stmt); + bool is_ne = gimple_assign_rhs_code (stmt) == NE_EXPR; + tree cst = constant_boolean_node (is_ne, TREE_TYPE (lhs)); + gimple_assign_set_rhs_with_ops (&gsi, TREE_CODE (cst), cst); + update_stmt (gsi_stmt (gsi)); + } + } + /* Conditional checking that return value of allocation is non-NULL can be turned to constant if the allocation itself is unnecessary. */
