From 2daa679a842a40c690b402f7691a64508a9b00d2 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Thu, 13 Apr 2017 16:27:54 -0400 Subject: [PATCH 06/10] FIXME: start adding C++ optimizations --- gcc/common.opt | 5 + gcc/doc/invoke.texi | 10 +- gcc/heap-optimizations.c | 163 ++++++++++++++++-- gcc/testsuite/g++.dg/heap-optimization.C | 285 +++++++++++++++++++++++++++++++ 4 files changed, 446 insertions(+), 17 deletions(-) create mode 100644 gcc/testsuite/g++.dg/heap-optimization.C diff --git a/gcc/common.opt b/gcc/common.opt index 4021622..af76d38 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -631,6 +631,11 @@ Wunsafe-loop-optimizations Common Var(warn_unsafe_loop_optimizations) Warning Warn if the loop cannot be optimized due to nontrivial assumptions. +Wmismatching-deallocator +Common Warning +Warn if the function used to deallocate memory does not match the correct +one given the function used to allocate memory. + Wmissing-noreturn Common Warning Alias(Wsuggest-attribute=noreturn) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8d3821e..2ef9db3 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -288,7 +288,7 @@ Objective-C and Objective-C++ Dialects}. -Winvalid-pch -Wlarger-than=@var{len} @gol -Wlogical-op -Wlogical-not-parentheses -Wlong-long @gol -Wmain -Wmaybe-uninitialized -Wmemset-elt-size -Wmemset-transposed-args @gol --Wmisleading-indentation -Wmissing-braces @gol +-Wmisleading-indentation -Wmismatching-deallocator -Wmissing-braces @gol -Wmissing-field-initializers -Wmissing-include-dirs @gol -Wno-multichar -Wnonnull -Wnonnull-compare @gol -Wnormalized=@r{[}none@r{|}id@r{|}nfc@r{|}nfkc@r{]} @gol @@ -4417,6 +4417,14 @@ about the layout of the file that the directive references. This warning is enabled by @option{-Wall} in C and C++. +@item -Wmismatching-deallocator +@opindex Wmismatching-deallocator +@opindex Wno-mismatching-deallocator +Warn if the function used to deallocate memory does not match the correct +one given the function used to allocate memory. +FIXME: give an example +FIXME: when to enable? + @item -Wmissing-braces @opindex Wmissing-braces @opindex Wno-missing-braces diff --git a/gcc/heap-optimizations.c b/gcc/heap-optimizations.c index 6c0c505..20f743e 100644 --- a/gcc/heap-optimizations.c +++ b/gcc/heap-optimizations.c @@ -39,16 +39,26 @@ along with GCC; see the file COPYING3. If not see //namespace { +/* FIXME. */ + +enum allocator_kind { + ALLOCATOR_KIND_MALLOC, + ALLOCATOR_KIND_NEW, + ALLOCATOR_KIND_VEC_NEW, + ALLOCATOR_KIND_UNKNOWN +}; + /* A matching pair of allocation/release calls. */ struct alloc_pair { - alloc_pair (gcall *acquire, gcall *release) - : m_acquire (acquire), m_release (release) + alloc_pair (gcall *acquire, gcall *release, enum allocator_kind kind) + : m_acquire (acquire), m_release (release), m_kind (kind) {} gcall *m_acquire; gcall *m_release; + enum allocator_kind m_kind; void maybe_convert_to_alloca (); }; @@ -133,9 +143,9 @@ class allocation_group basic_block get_acquire_bb () const { return m_acquire_bb; } basic_block get_release_bb () const { return m_release_bb; } - void add_alloc_pair (gcall *alloc, gcall *release) + void add_alloc_pair (gcall *alloc, gcall *release, enum allocator_kind kind) { - m_alloc_pairs.safe_push (alloc_pair (alloc, release)); + m_alloc_pairs.safe_push (alloc_pair (alloc, release, kind)); } void combine_calls (); @@ -154,7 +164,7 @@ class allocation_group class function_state { public: - void add_alloc_pair (gcall *alloc, gcall *release); + void add_alloc_pair (gcall *alloc, gcall *release, enum allocator_kind kind); void combine_calls (); @@ -193,8 +203,10 @@ class pass_heap_optimizations : public gimple_opt_pass unsigned int execute (function *) OVERRIDE FINAL; private: - void handle_malloc (gcall *call); + //void handle_malloc (gcall *call); void handle_free (function_state &state, gcall *call); + void handle_operator_delete (function_state &state, gcall *call); + void handle_operator_vec_delete (function_state &state, gcall *call); void maybe_optimize_alloca (gcall *call); }; // class pass_heap_optimizations @@ -303,7 +315,8 @@ allocation_group::sort_by_alloc_order () void -function_state::add_alloc_pair (gcall *alloc, gcall *release) +function_state::add_alloc_pair (gcall *alloc, gcall *release, + enum allocator_kind kind) { gcc_assert (alloc); gcc_assert (release); @@ -314,7 +327,7 @@ function_state::add_alloc_pair (gcall *alloc, gcall *release) #endif allocation_group &group = get_allocation_group (alloc->bb, release->bb); - group.add_alloc_pair (alloc, release); + group.add_alloc_pair (alloc, release, kind); } void @@ -370,6 +383,45 @@ pass_heap_optimizations::execute (function *fun) /* Iterate over statements, looking for function calls. */ gimple *stmt = gsi_stmt (si); + if (gcall *call = dyn_cast (stmt)) + { + //debug (call); + tree fndecl = gimple_call_fndecl (call); + //debug_tree (fndecl); + /* FIXME: Nasty hack: */ + if (fndecl && DECL_NAME (fndecl)) + { + if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), + "operator delete")) + handle_operator_delete (state, call); + if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), + "operator delete []")) + handle_operator_vec_delete (state, call); + } + + //debug_tree (gimple_call_arg (stmt, 0)); + // FIXME: how to detect operator delete[] and operator delete ? + // maybe with attributes?: tag the release function with the + // allocator function, and, optionally a realloc function + /* cp/decl.c:cxx_init_decl_processing: has: + push_cp_library_fn (DELETE_EXPR, deltype, ECF_NOTHROW); + push_cp_library_fn (VEC_DELETE_EXPR, deltype, ECF_NOTHROW);. */ + ///* True if NAME is the IDENTIFIER_NODE for an overloaded "operator + // new" or "operator delete". */ + /* +#define NEW_DELETE_OPNAME_P(NAME) \ + ((NAME) == cp_operator_id (NEW_EXPR) \ + || (NAME) == cp_operator_id (VEC_NEW_EXPR) \ + || (NAME) == cp_operator_id (DELETE_EXPR) \ + || (NAME) == cp_operator_id (VEC_DELETE_EXPR)) + +#define cp_operator_id(CODE) \ + (operator_name_info[(int) (CODE)].identifier) + */ + + + } + #if 0 if (gimple_call_builtin_p (stmt, BUILT_IN_MALLOC)) handle_malloc (as_a (stmt)); @@ -410,6 +462,7 @@ pass_heap_optimizations::execute (function *fun) /* Merging mallocs/frees. */ /* If we have a set of malloc in one BB and a set of frees in one BB... */ +#if 0 void pass_heap_optimizations::handle_malloc (gcall *call) { @@ -419,18 +472,46 @@ pass_heap_optimizations::handle_malloc (gcall *call) debug_tree (size); //warning_at (call->location, 0, "arg0 size: %qE", size); } +#endif /* FIXME. */ +static enum allocator_kind +get_allocator_kind (gcall *call) +{ + if (gimple_call_builtin_p (call, BUILT_IN_MALLOC)) + return ALLOCATOR_KIND_MALLOC; -void -pass_heap_optimizations::handle_free (function_state &state, gcall *call) + tree fndecl = gimple_call_fndecl (call); + + // FIXME: nasty hack + if (fndecl && DECL_NAME (fndecl)) + { + if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), + "operator new")) + return ALLOCATOR_KIND_NEW; + if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), + "operator new []")) + return ALLOCATOR_KIND_VEC_NEW; + } + + /* Not recognized. */ + return ALLOCATOR_KIND_UNKNOWN; +} + + +/* FIXME. */ + +static void +handle_release (function_state &state, gcall *release_call, + enum allocator_kind release_kind) { + gcc_assert (release_kind != ALLOCATOR_KIND_UNKNOWN); //warning_at (call->location, 0, "found free"); - tree ptr = gimple_call_arg (call, 0); + tree ptr = gimple_call_arg (release_call, 0); //warning_at (call->location, 0, "arg0 ptr: %qE", ptr); - /* Try to match the ptr up with a malloc call. */ + /* Try to match the ptr up with an allocator call. */ if (TREE_CODE (ptr) != SSA_NAME) return; @@ -438,11 +519,56 @@ pass_heap_optimizations::handle_free (function_state &state, gcall *call) if (!def_stmt) return; - if (gimple_call_builtin_p (def_stmt, BUILT_IN_MALLOC)) + gcall *allocator_call = dyn_cast (def_stmt); + if (!allocator_call) + return; + + enum allocator_kind acquire_kind = get_allocator_kind (allocator_call); + + if (acquire_kind == ALLOCATOR_KIND_UNKNOWN) + return; + + if (acquire_kind != release_kind) { - //warning_at (def_stmt->location, 0, "found matching malloc"); - state.add_alloc_pair (as_a (def_stmt), call); + if (warning_at (release_call->location, + OPT_Wmismatching_deallocator, + "mismatching deallocator...")) + { + inform (allocator_call->location, "...for allocation here"); + // FIXME: fix-it hint? + // FIXME: more test cases: + // e.g. where it's unknown + // e.g. for realloc + } + return; } + + warning_at (def_stmt->location, 0, "found matching pair"); + state.add_alloc_pair (allocator_call, release_call, acquire_kind); +} + +/* FIXME. */ + +void +pass_heap_optimizations::handle_free (function_state &state, gcall *call) +{ + handle_release (state, call, ALLOCATOR_KIND_MALLOC); +} + +void +pass_heap_optimizations::handle_operator_delete (function_state &state, + gcall *call) +{ + warning_at (call->location, 0, "found delete"); + handle_release (state, call, ALLOCATOR_KIND_NEW); +} + +void +pass_heap_optimizations::handle_operator_vec_delete (function_state &state, + gcall *call) +{ + warning_at (call->location, 0, "found delete[]"); + handle_release (state, call, ALLOCATOR_KIND_VEC_NEW); } /* FIXME. @@ -576,8 +702,13 @@ pass_heap_optimizations::maybe_optimize_alloca (gcall *call) path after the malloc i.e. if the free's block is dominated by the malloc's block. */ -#endif +/* TODO: warn about mismatched acquire/release (e.g. new[] + with delete without []). */ + +/* TODO: c++ stdlib unique_ptr vs shared_ptr etc: ensure we optimize + common patterns. */ +#endif //} // anon namespace diff --git a/gcc/testsuite/g++.dg/heap-optimization.C b/gcc/testsuite/g++.dg/heap-optimization.C new file mode 100644 index 0000000..42f1311 --- /dev/null +++ b/gcc/testsuite/g++.dg/heap-optimization.C @@ -0,0 +1,285 @@ +/* { dg-do compile } */ +/* { dg-options "-fheap-optimizations" } */ + +#include + +typedef unsigned int edit_distance_t; + +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) + +edit_distance_t +levenshtein_distance (const char *s, int len_s, + const char *t, int len_t) +{ + const bool debug = false; + + if (len_s == 0) + return len_t; + if (len_t == 0) + return len_s; + + edit_distance_t *v0 = new edit_distance_t[len_s + 1]; + edit_distance_t *v1 = new edit_distance_t[len_s + 1]; + + for (int i = 0; i < len_s + 1; i++) + v0[i] = i; + + for (int i = 0; i < len_t; i++) + { + v1[0] = i + 1; + + for (int j = 0; j < len_s; j++) + { + edit_distance_t cost = (s[j] == t[i] ? 0 : 1); + edit_distance_t deletion = v1[j] + 1; + edit_distance_t insertion = v0[j + 1] + 1; + edit_distance_t substitution = v0[j] + cost; + edit_distance_t cheapest = MIN (deletion, insertion); + cheapest = MIN (cheapest, substitution); + v1[j + 1] = cheapest; + } + + for (int j = 0; j < len_s + 1; j++) + v0[j] = v1[j]; + } + + edit_distance_t result = v1[len_s]; + delete[] v0; + delete[] v1; + return result; +} + +/* Unlike the malloc/free case, this wasn't optimized away. + TODO: Conversion to alloca */ + +int unboxing (int x, int y) +{ + int *ptr_a, *ptr_b; + int result; + + ptr_a = new int; + *ptr_a = x; + + ptr_b = new int; + *ptr_b = y; + + result = *ptr_a + *ptr_b; + + delete ptr_a; + delete ptr_b; + + return result; +} + +/* PR tree-optimization/21046. */ + +extern void foo (char *p, int i); + +void hoist_alloc_1 (void) +{ + char *p; + int i; + + for (i = 0; i < 10; i++) + { + p = new char[20]; + foo (p, i); + delete[] p; + } +} + +#if 0 +void hoist_alloc_2 (void) +{ + char *p; + int i; + + for (i = 0; i < 10; i++) + { + p = new char[2 * i + 2]; + foo (p, i); + delete[] (p); + } +} +#endif + +/* PR middle-end/53082. */ + +#if 0 +void test_redundant_1 (void) +{ + int i; + for (i = 0; i < 100; i++) + { + int *s = new int; + *s = 10; + delete (s); + } +} +#endif + +#if 0 +void test_combined (void) +{ + int i; + for (i = 0; i < 100; i++) + { + int *s1 = new int; + int *s2 = new int; + int *s3 = new int; + + delete(s1); delete(s2); delete(s3); + } +} +#endif + +void test_mismatches () +{ + free ((int *)malloc (sizeof(int))); + delete ((int *)malloc (sizeof (int))); + delete[] ((int *)malloc (sizeof (int))); + + free (new int); + //delete (new int); + delete[] (new int); + + free (new int[10]); + delete (new int[10]); + delete[] (new int[10]); + + // FIXME: Can we give a fixit hint to add the [] to the delete here? + int *ptr = new int[10]; + delete ptr; +} + +enum kind { KIND_INT, KIND_FLOAT }; + +typedef struct value +{ + enum kind kind; + union { + int u_int; + float u_float; + } u; +} value; + +value *value_new_int (int i) +{ + value *v = new value; + v->kind = KIND_INT; + v->u.u_int = i; + return v; +} + +value *value_new_float (float f) +{ + value *v = new value; + v->kind = KIND_FLOAT; + v->u.u_float = f; + return v; +} + +void value_free (value *v) +{ + delete v; +} + +int value_as_int (value *v_in) +{ + switch (v_in->kind) + { + case KIND_INT: + return v_in->u.u_int; + case KIND_FLOAT: + return v_in->u.u_float; + } +} + +float value_as_float (value *v_in) +{ + switch (v_in->kind) + { + case KIND_INT: + return v_in->u.u_int; + case KIND_FLOAT: + return v_in->u.u_float; + } +} + +value *times_two (value *v_in) +{ + switch (v_in->kind) + { + case KIND_INT: + return value_new_int (v_in->u.u_int * 2); + case KIND_FLOAT: + return value_new_float (v_in->u.u_float * 2.); + } +} + +value *value_sub (value *v_x, value *v_y) +{ + switch (v_x->kind) + { + case KIND_INT: + return value_new_int (v_x->u.u_int - value_as_int (v_y)); + case KIND_FLOAT: + return value_new_float (v_x->u.u_float - value_as_float (v_y)); + } +} + +value *value_mult (value *v_x, value *v_y) +{ + switch (v_x->kind) + { + case KIND_INT: + return value_new_int (v_x->u.u_int * value_as_int (v_y)); + case KIND_FLOAT: + return value_new_float (v_x->u.u_float * value_as_float (v_y)); + } +} + +int dynamic_times_two (int i) +{ + value *v = value_new_int (i); + value *d = times_two (v); + int result = d->u.u_int; + free (d); + free (v); + return result; +} + +value *dynamic_discriminant (value *a, value *b, value *c) +{ + /* b^2 - 4 * a * c */ + value *b_squared = value_mult (b, b); + value *const_4 = value_new_float (4.); + value *four_a = value_mult (const_4, a); + value *four_a_c = value_mult (four_a, c); + value *diff = value_sub (b_squared, four_a_c); + + value_free (b_squared); + value_free (const_4); + value_free (four_a); + value_free (four_a_c); + + return diff; +} + +float discriminant (float a, float b, float c) +{ + value *v_a = value_new_float (a); + value *v_b = value_new_float (b); + value *v_c = value_new_float (c); + + value *v_result = dynamic_discriminant (v_a, v_b, v_c); + + float result = value_as_float (v_result); + + value_free (v_a); + value_free (v_b); + value_free (v_c); + value_free (v_result); + + return result; +} -- 1.8.5.3