From 737d6314fe7b18e2cc68d7eff402d70f5cadda4d Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 11 Apr 2017 16:12:08 -0400
Subject: [PATCH 2/2] FIXME: initial WIP of heap optimizations

---
 gcc/Makefile.in                                |    1 +
 gcc/builtins.def                               |    2 +
 gcc/common.opt                                 |   15 +
 gcc/doc/invoke.texi                            |   38 +-
 gcc/heap-optimizations.c                       | 1255 ++++++++++++++++++++++++
 gcc/params.def                                 |    5 +
 gcc/passes.def                                 |    7 +
 gcc/testsuite/c-c++-common/heap-optimization.c | 1029 +++++++++++++++++++
 gcc/testsuite/c-c++-common/obstack-builtins.c  |  181 ++++
 gcc/testsuite/g++.dg/heap-idioms.C             |   54 +
 gcc/testsuite/g++.dg/heap-optimization.C       |  288 ++++++
 gcc/testsuite/g++.dg/heap-with-exceptions.C    |  144 +++
 gcc/timevar.def                                |    1 +
 gcc/tree-pass.h                                |    1 +
 pr38318-3.f90                                  |   17 +
 pr38318-main.f90                               |   30 +
 pr38318-test.f90                               |   26 +
 pr42958-c17.f90                                |    7 +
 pr42958-c18.f90                                |    5 +
 pr42958-c23.c                                  |   75 ++
 pr42958-tonto.f90                              |   64 ++
 test.c                                         |   51 +
 22 files changed, 3294 insertions(+), 2 deletions(-)
 create mode 100644 gcc/heap-optimizations.c
 create mode 100644 gcc/testsuite/c-c++-common/heap-optimization.c
 create mode 100644 gcc/testsuite/c-c++-common/obstack-builtins.c
 create mode 100644 gcc/testsuite/g++.dg/heap-idioms.C
 create mode 100644 gcc/testsuite/g++.dg/heap-optimization.C
 create mode 100644 gcc/testsuite/g++.dg/heap-with-exceptions.C
 create mode 100644 pr38318-3.f90
 create mode 100644 pr38318-main.f90
 create mode 100644 pr38318-test.f90
 create mode 100644 pr42958-c17.f90
 create mode 100644 pr42958-c18.f90
 create mode 100644 pr42958-c23.c
 create mode 100644 pr42958-tonto.f90
 create mode 100644 test.c

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 50d84bf..0a0de4e 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1328,6 +1328,7 @@ OBJS = \
 	haifa-sched.o \
 	hash-map-tests.o \
 	hash-set-tests.o \
+	heap-optimizations.o \
 	hsa-common.o \
 	hsa-gen.o \
 	hsa-regalloc.o \
diff --git a/gcc/builtins.def b/gcc/builtins.def
index e383c80..c259415 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -864,6 +864,8 @@ DEF_GCC_BUILTIN        (BUILT_IN_LONGJMP, "longjmp", BT_FN_VOID_PTR_INT, ATTR_NO
 /* [trans-mem]: Adjust BUILT_IN_TM_MALLOC if BUILT_IN_MALLOC is changed.  */
 DEF_LIB_BUILTIN        (BUILT_IN_MALLOC, "malloc", BT_FN_PTR_SIZE, ATTR_MALLOC_SIZE_1_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_NEXT_ARG, "next_arg", BT_FN_PTR_VAR, ATTR_LEAF_LIST)
+DEF_GCC_BUILTIN        (BUILT_IN_OBSTACK_ALLOC, "obstack_alloc", BT_FN_PTR_SIZE, ATTR_MALLOC_SIZE_1_NOTHROW_LEAF_LIST)
+DEF_GCC_BUILTIN        (BUILT_IN_OBSTACK_DEALLOC, "obstack_dealloc", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_PARITY, "parity", BT_FN_INT_UINT, ATTR_CONST_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_PARITYIMAX, "parityimax", BT_FN_INT_UINTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_PARITYL, "parityl", BT_FN_INT_ULONG, ATTR_CONST_NOTHROW_LEAF_LIST)
diff --git a/gcc/common.opt b/gcc/common.opt
index 4021622..56de9c8 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -496,6 +496,12 @@ Driver Negative(Qn)
 R
 Driver Joined Separate
 
+fremarks-heap-optimizations
+Common Remark Var(flag_remarks_heap_optimizations)
+FIXME: description
+FIXME: would use -Rheap-optimizations but -R for driver seems to swallow the option
+FIXME: might be the same for options beginning with -r; not sure
+
 S
 Driver
 
@@ -631,6 +637,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)
 
@@ -1453,6 +1464,10 @@ fgraphite-identity
 Common Report Var(flag_graphite_identity) Optimization
 Enable Graphite Identity transformation.
 
+fheap-optimizations
+Common Report Var(flag_heap_optimizations) Optimization
+FIXME
+
 fhoist-adjacent-loads
 Common Report Var(flag_hoist_adjacent_loads) Optimization
 Enable hoisting adjacent loads to encourage generating conditional move
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 8d3821e..84a4968 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -141,6 +141,7 @@ only one of these two forms, whichever one is not the default.
 * Debugging Options::   Producing debuggable code.
 * Optimize Options::    How much optimization?
 * Instrumentation Options:: Enabling profiling and extra run-time error checking.
+* Remarks::             Details on how your code is being optimized.
 * Preprocessor Options:: Controlling header files and macro definitions.
                          Also, getting dependency information for Make.
 * Assembler Options::   Passing options to the assembler.
@@ -288,7 +289,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
@@ -369,7 +370,7 @@ Objective-C and Objective-C++ Dialects}.
 -ffast-math  -ffinite-math-only  -ffloat-store  -fexcess-precision=@var{style} @gol
 -fforward-propagate  -ffp-contract=@var{style}  -ffunction-sections @gol
 -fgcse  -fgcse-after-reload  -fgcse-las  -fgcse-lm  -fgraphite-identity @gol
--fgcse-sm  -fhoist-adjacent-loads  -fif-conversion @gol
+-fgcse-sm  -fhoist-adjacent-loads  -fheap-optimizations  -fif-conversion @gol
 -fif-conversion2  -findirect-inlining @gol
 -finline-functions  -finline-functions-called-once  -finline-limit=@var{n} @gol
 -finline-small-functions  -fipa-cp  -fipa-cp-clone @gol
@@ -464,6 +465,10 @@ Objective-C and Objective-C++ Dialects}.
 -finstrument-functions-exclude-function-list=@var{sym},@var{sym},@dots{} @gol
 -finstrument-functions-exclude-file-list=@var{file},@var{file},@dots{}}
 
+@item Remarks
+@xref{Remarks,,Options to Control Remarks from the Compiler}.
+@gccoptlist{-fremarks-heap-optimizations}
+
 @item Preprocessor Options
 @xref{Preprocessor Options,,Options Controlling the Preprocessor}.
 @gccoptlist{-A@var{question}=@var{answer} @gol
@@ -4417,6 +4422,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
@@ -8041,6 +8054,11 @@ loads are from adjacent locations in the same structure and the target
 architecture has a conditional move instruction.  This flag is enabled
 by default at @option{-O2} and higher.
 
+@item -fheap-optimizations
+@opindex fheap-optimizations
+FIXME
+FIXME enable by default somewhere?
+
 @item -ftree-copy-prop
 @opindex ftree-copy-prop
 Perform copy propagation on trees.  This pass eliminates unnecessary
@@ -11406,6 +11424,22 @@ using universal character names.
 @end table
 
 
+@node Remarks
+@section Options to Control Remarks from the Compiler
+@cindex remarks
+@cindex options, remarks
+
+These options are aimed at advanced users who may be interested
+in seeing additional diagnostics from the compiler, giving information
+on the decisions it is making on the code.
+
+@table @gcctabopt
+@item -fremarks-heap-optimizations
+@opindex fremarks-heap-optimizations
+FIXME
+
+@end table
+
 @node Preprocessor Options
 @section Options Controlling the Preprocessor
 @cindex preprocessor options
diff --git a/gcc/heap-optimizations.c b/gcc/heap-optimizations.c
new file mode 100644
index 0000000..de9a46c
--- /dev/null
+++ b/gcc/heap-optimizations.c
@@ -0,0 +1,1255 @@
+/* Heap optimizations.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+// FIXME: purge this list:
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-fold.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic-core.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "tree-object-size.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "print-tree.h"
+#include "intl.h"
+#include "cfgloop.h"
+#include "tree-data-ref.h"
+#include "sese.h"
+
+/* Look for heap calls:
+     - "malloc"/"free"
+     - "operator new"/"operator delete"
+     - "operator new[]"/"operator delete[]"
+
+   and attempt to convert them to something more specialized.
+
+   We attempt to detect heap buffers that have a lifetime
+   bounded by control flow through a function, such as:
+
+     ptr = malloc (sz);
+
+     [...stuff happens, potentially including control flow...]
+
+     free (ptr);
+
+   where every path from the allocation site passes through
+   the deallocation site: the lifetime of the underlying buffer
+   is guarded by the two callsites.
+
+   FIXME: I believe if we use SSA names, then we can assume that the
+   buffer is not realloc-ed (FIXME: can we?)
+
+   Assuming we have sufficiently sane alloc/dealloc pairs, we can
+   convert them to use a (thread local) obstack, provided:
+     - every path through the function that passes through the alloc
+       also passes through the dealloc, and vice versa (there are
+       some awkward special-cases to do with error-handling
+       and exception-handling; see the notes on class alloc_pair below).
+     - pointer can't be realloc-ed
+     - pointer can't be seen by another thread (though if we're freeing
+       it, isn't that undefined behavior?)
+
+   We can potentially use alloca/local variables for sufficiently
+   small allocations that are not in loops.
+
+   FIXME: older notes:
+
+     Can the allocation escape?
+
+     If not known to be small enough, convert to:
+       if (size < limit)
+          ptr = __builtin_alloca (size)
+       else
+          ptr =
+     Otherwise...
+*/
+
+/* TODO: warn about mismatched alloc/release (e.g. new[]
+   with delete without []).  */
+
+/* TODO: c++ stdlib unique_ptr vs shared_ptr etc: ensure we optimize
+   common patterns.  */
+
+/* TODO: -ffortity-alloca : detect alloca calls with either non-const
+   size, or size above a param, and convert them to malloc calls.
+   Potentially turn it into logic (using alloca below the threshold, malloc
+   above, with a run time conditional, adding a conditional free call).  */
+
+/* FIXME: presumably the BBs need to nest properly for us to be able to
+   use a stack.  */
+
+/* FIXME.  */
+//namespace {
+
+/* Declarations.  */
+
+static bool
+heap_remark (location_t loc, const char *gmsgid, ...)
+  ATTRIBUTE_GCC_DIAG(2, 3);
+
+/* A family of allocator/deallocator calls.  */
+
+enum allocator_family {
+  /* malloc, free, calloc, realloc.  */
+  ALLOCATOR_FAMILY_MALLOC,
+
+  /* C++ operator new/delete.  */
+  ALLOCATOR_FAMILY_NEW,
+
+  /* C++ operator new[]/delete[].  */
+  ALLOCATOR_FAMILY_VEC_NEW,
+
+  /* Anything else.  */
+  ALLOCATOR_FAMILY_UNKNOWN
+};
+
+/* An "alloc_pair" is a matching pair of acquire/release calls,
+   within the same allocation family, and for which the lifetime
+   of the allocated buffer can be directly tracked by the control
+   flow of the code.
+
+   For example:
+
+      ptr = malloc (N);  <- ACQUIRE call
+      [...]
+      free (ptr); <- RELEASE call
+
+   along with simple error-checking e.g.:
+
+      ptr = malloc (32); <- ACQUIRE call
+      if (ptr)
+	{
+	  [...]
+	  free (ptr); <- RELEASE call
+	}
+
+   See lifetime_corresponds_to_control_flow_p for the flow-control
+   conditions.
+
+   FIXME: this doesn't work for C++ code with exceptions enabled.
+   For example:
+
+     ptr = new foo ();
+     ptr->do_stuff ();
+     delete ptr;
+
+   becomes (by the time it's seen by the heap pass):
+
+    <bb 2>:
+      _4 = operator new (64);
+      foo::foo (_4);
+      succ edges: <bb 3> normally, <bb 4> for exceptions
+
+    <bb 3>:
+      foo::do_stuff (_4);
+      foo::~foo (_4);
+      operator delete (_4, 64);
+      return;
+
+    <bb 4>:
+      operator delete (_4, 64);
+      resx 1
+
+   so that there are effectively two deallocator calls for the
+   allocator call.   Perhaps we somehow need to support multiple
+   deallocator calls per allocator call.  */
+
+struct alloc_pair
+{
+  alloc_pair (gcall *acquire, gcall *release, enum allocator_family family)
+  : m_acquire (acquire), m_release (release), m_family (family)
+  {}
+
+  gcall *m_acquire;
+  gcall *m_release;
+  enum allocator_family m_family;
+
+  void maybe_optimize ();
+
+private:
+  bool could_be_reallocated () const;
+  bool can_convert_to_alloca_p () const;
+  void convert_to_alloca ();
+  void convert_to_obstack ();
+};
+
+/* Combining mallocs.
+
+   FIXME: this isn't true anymore...
+   FIXME: ...as we're more interested in converting to obstacks
+
+   Given:
+
+      ptr0 = malloc (sz0);
+      ptr1 = malloc (sz1);
+      ...
+      ptrN = malloc (szN);
+
+  where all are within the same BB, and all are freed
+  within (a potentially different) same BB, convert it to:
+
+      tot1 = sz0 + sz1;
+      ...
+      totN = szN_1 + szN;
+      ptr_combined = malloc (totN);
+      ptr0 = ptr_combined;
+      ptr1 = ptr_combined + sz0; (as a byte offset)
+      ...
+      ptrN = ptr_combined + (sz0 + sz1 + ... + szN_1);
+
+  and eliminate all of the free calls, apart from a:
+
+      free (ptr_combined);
+
+  Combine it based on frees within BBs: for each set of frees in a BB,
+  figure out the mallocs per BB.
+
+  What about the aliasing implications, though?
+ */
+
+/* A pair of basic blocks: a group of allocations calls in
+   one basic block, and a matching set of deallocation calls in
+   the other basic block.
+   There might be different allocator families in the group,
+   but within any given alloc_pair, the allocator family
+   is consistent.  */
+
+class allocation_group
+{
+ public:
+  allocation_group (basic_block acquire_bb, basic_block release_bb)
+  : m_acquire_bb (acquire_bb), m_release_bb (release_bb)
+  {}
+
+  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 *acquire, gcall *release,
+		       enum allocator_family family)
+  {
+    m_alloc_pairs.safe_push (alloc_pair (acquire, release, family));
+  }
+
+  void optimize_heap_calls ();
+
+ private:
+  void sort_by_acquisition_order ();
+
+ private:
+  basic_block m_acquire_bb;
+  basic_block m_release_bb;
+  auto_vec<alloc_pair> m_alloc_pairs;
+};
+
+/* A class encapsulating information on all of the optimizable
+   heap calls in a function.  */
+
+class function_state
+{
+ public:
+  void find_heap_calls (function *fun);
+  void optimize_heap_calls ();
+
+ private:
+  void handle_release (gcall *release_call,
+		       enum allocator_family release_family);
+
+  void add_alloc_pair (gcall *acquire, gcall *release,
+		       enum allocator_family family);
+
+  allocation_group &get_allocation_group (basic_block bb_acquire,
+					  basic_block bb_release);
+
+ private:
+  // FIXME: probably need a more efficient data structure here.
+  auto_vec<allocation_group> m_allocation_groups;
+};
+
+/* Pass data.  */
+
+const pass_data pass_data_heap_optimizations =
+{
+  GIMPLE_PASS, /* type */
+  "heap", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_HEAP_OPTIMIZATIONS, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+/* The "heap" pass.  */
+
+class pass_heap_optimizations : public gimple_opt_pass
+{
+ public:
+  pass_heap_optimizations (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_heap_optimizations, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) OVERRIDE FINAL
+  {
+    return flag_heap_optimizations;
+  }
+
+  unsigned int execute (function *) OVERRIDE FINAL;
+
+ private:
+  void optimize_alloca_calls (function *fun);
+  void maybe_optimize_alloca (gcall *call);
+}; // class pass_heap_optimizations
+
+/* Implementation.  */
+
+/* Issue an optimization remark at LOC, guarded by
+   -fremarks-heap-optimizations.  */
+
+static bool
+heap_remark (location_t loc, const char *gmsgid, ...)
+{
+  if (1)
+    {
+      va_list ap;
+      va_start (ap, gmsgid);
+      bool ret = remark_va (loc, OPT_fremarks_heap_optimizations, gmsgid, &ap);
+      va_end (ap);
+      return ret;
+    }
+}
+
+/* class alloc_pair.  */
+
+/* Attempt to optimize the given alloc_pair.  Ideally the alloc_pair
+   is optimized to an alloca (eliminating the explicit release),
+   failing that, to a thread-local obstack implementation.  */
+
+void
+alloc_pair::maybe_optimize ()
+{
+#if 0
+  /* We can't optimize allocations that could have "realloc" called
+     on them.  */
+  if (could_be_reallocated ())
+    {
+      heap_remark (m_acquire->location,
+		   "can't optimize: can't prove allocation is not reallocated");
+      return;
+    }
+#endif
+
+  /* FIXME: do we need to prove that the pointer does not escape?
+     If we have an alloc/dealloc pair, then it's already undefined behavior
+     for another thread to be using the pointer.  */
+
+  /* FIXME: any other conditions?  */
+
+  if (can_convert_to_alloca_p ())
+    convert_to_alloca ();
+  else
+    convert_to_obstack ();
+}
+
+/* Determine if the buffer could be reallocated at some point
+   between the allocator and deallocator calls.  */
+
+bool
+alloc_pair::could_be_reallocated () const
+{
+  /* "realloc" is only valid for ALLOCATOR_FAMILY_MALLOC;
+     it's undefined behavior to use "realloc" with the C++ families.  */
+  if (m_family != ALLOCATOR_FAMILY_MALLOC)
+    return false;
+
+  // TODO: some kind of analysis, possibly interprocedural
+
+  /* Presumably we can also assume that a const ptr doesn't get realloced,
+     which could help a lot to avoid IPA detection of realloc - though
+     it could still escape.  */
+
+  // FIXME: for now, be conservative, and don't optimize
+  return true;
+}
+
+/* Assuming that the ptr has already been proven not to escape to other
+   threads, and that no realloc calls can be made on the ptr, determine
+   if this alloc_pair is suitable for converting to alloca.
+   It must be a fixed-size allocation, below the FIXME threshold, and must
+   not be within a loop.  */
+
+bool
+alloc_pair::can_convert_to_alloca_p () const
+{
+  /* Must be a fixed-size allocation.  */
+   tree size = gimple_call_arg (m_acquire, 0);
+   if (TREE_CODE (size) != INTEGER_CST)
+     {
+       heap_remark (m_acquire->location,
+		    "can't convert to alloca: size is not constant");
+       return false;
+     }
+
+   /* Must be below a threshold param.  */
+  unsigned HOST_WIDE_INT uhwi_size = tree_to_uhwi (size);
+  unsigned HOST_WIDE_INT max_alloca_opt_size
+    = PARAM_VALUE (PARAM_MAX_ALLOCA_OPTIMIZATION_SIZE);
+  if (uhwi_size > max_alloca_opt_size)
+    {
+      heap_remark (m_acquire->location,
+		   "can't convert to alloca: size (%wu) is above %wu"
+		   " (%<--param=max-alloca-optimization-size%>)",
+		   uhwi_size, max_alloca_opt_size);
+      return false;
+    }
+
+  /* Must not be within a loop.  */
+  if (m_acquire->bb->loop_father->num)
+    {
+      heap_remark (m_acquire->location,
+		   "can't convert to alloca: allocation is within a loop");
+      return false;
+    }
+  if (m_release->bb->loop_father->num)
+    {
+      heap_remark (m_release->location,
+		   "can't convert to alloca: deallocation is within a loop");
+      return false;
+    }
+
+  // FIXME:
+  //flow_loops_dump (stderr, NULL, 1);
+  /* FIXME: what if there's some weird control flow that ins't
+     identified as a loop, and which can cause the stmts to be
+     executed an arbitrary number of times?  */
+
+  return true;
+}
+
+/* FIXME.  */
+
+void
+alloc_pair::convert_to_alloca ()
+{
+  /* Convert the acquire stmt into an alloca call.  */
+  heap_remark (m_acquire->location, "converting to alloca");
+  tree alloca_decl = builtin_decl_explicit (BUILT_IN_ALLOCA);
+  // FIXME: should this be builtin_decl_implicit?
+  gimple_call_set_fndecl (m_acquire, alloca_decl);
+
+  /* FIXME: */
+  // do we want to capture inlining information in locations?
+  // maybe only when rich dumping is turned on?
+
+  // FIXME: gimple_call_set_alloca_for_var (gcall *s, bool for_var)
+
+  /* Delete the release stmt.  */
+  heap_remark (m_release->location, "removing explicit deallocation call");
+  gimple_stmt_iterator gsi = gsi_for_stmt (m_release);
+  /* bool require_eh_edge_purge = */
+  gsi_remove (&gsi, false /* true*/);
+  unlink_stmt_vdef (m_release);
+  release_defs (m_release);
+}
+
+/* FIXME.  */
+
+void
+alloc_pair::convert_to_obstack ()
+{
+  /* Convert to thread-local allocator.  */
+  heap_remark (m_acquire->location, "converting to obstack allocator");
+  tree obstack_alloc = builtin_decl_implicit (BUILT_IN_OBSTACK_ALLOC);
+  /* FIXME: this is NULL for gfortran.  */
+  //debug_tree (obstack_alloc);
+  gimple_call_set_fndecl (m_acquire, obstack_alloc);
+
+  heap_remark (m_release->location, "converting to obstack deallocator");
+  tree obstack_dealloc = builtin_decl_implicit (BUILT_IN_OBSTACK_DEALLOC);
+  //debug_tree (obstack_dealloc);
+  gimple_call_set_fndecl (m_release, obstack_dealloc);
+  // FIXME: convert from ptr to size argument???
+
+
+  // TODO
+  // (this ought to be able to be callable within a loop, and to cope
+  // with non-const sizes)
+  // FIXME: this assumes that release calls happen in reverse order
+  // of acquisition calls; need to check this (and potentially reorder them)
+}
+
+/* class allocation_group.  */
+
+/* FIXME.  */
+
+void
+allocation_group::optimize_heap_calls ()
+{
+  heap_remark (UNKNOWN_LOCATION,
+	       "optimizing heap calls with alloc in BB: %i, dealloc in BB: %i",
+	       m_acquire_bb->index, m_release_bb->index);
+
+  /* FIXME: Possible hoisting opportunities.
+     Should we attempt hoisting here, or leave it to the loop passes?
+     The regular hoisting optimization "lim" happens after "dom", and "dom" can
+     duplicate allocation calls, which prevents the alloc_pair detection from
+     working.  So we may want to do our own hoisting, for
+     const-sized allocations, perhaps?  */
+  if (m_acquire_bb->loop_father == m_release_bb->loop_father)
+    if (m_acquire_bb->loop_father->num)
+      {
+	heap_remark (UNKNOWN_LOCATION,
+		     "FIXME: alloc/dealloc calls are in loop %i; hoist them?",
+		     m_acquire_bb->loop_father->num);
+	/* FIXME.  */
+	/* Presumably here we can only hoist const-sized allocations, or those
+	   for which the size of the allocation is invariant.  */
+      }
+
+  /* m_alloc_pairs is in order of the releases, but we need to process
+     the acquire calls in the order they occur in.  */
+  sort_by_acquisition_order ();
+
+  alloc_pair *pair;
+  unsigned i;
+#if 0
+  FOR_EACH_VEC_ELT (m_alloc_pairs, i, pair)
+    {
+      warning_at (pair->m_acquire->location, 0, "combining acquire here");
+      warning_at (pair->m_release->location, 0, "combining release here");
+    }
+#endif
+
+  /* TODO:
+     test cases:
+     - involving the need for a cast on the sizeof
+     - different orders of malloc vs free
+     - use of ptrs before the allocs are used
+     - this can't work in the presence of realloc: detect realloc
+     - usage of calloc.  */
+
+  // DEAD CODE relating to attempts to combine allocs
+#if 0
+  gimple_seq seq = NULL;
+
+#if 0
+  /* Sum the sizes of the allocations.  */
+  // FIXME: this assumes that all sizes are available before any ptrs are used.
+  tree sum = size_zero_node;
+  FOR_EACH_VEC_ELT (m_alloc_pairs, i, pair)
+    {
+      tree alloc_size = gimple_call_arg (pair->m_acquire, 0);
+      tree cast = gimple_build (&seq, pair->m_acquire->location,
+				NOP_EXPR, size_type_node, alloc_size);
+      tree next_sum = gimple_build (&seq, pair->m_acquire->location,
+				    PLUS_EXPR, size_type_node, sum, cast);
+      sum = next_sum;
+    }
+#endif
+
+  // gcall *gimple_build_call_internal (enum internal_fn, unsigned, ...);
+
+
+#if 0
+  for (int j = 0; j < 5; j++)
+    {
+      gimple *stmt = gimple_build_assign (make_ssa_name (size_type_node),
+					  PLUS_EXPR, size_zero_node, // FIXME
+					  size_zero_node); // FIXME
+      gimple_seq_add_stmt (&seq, stmt);
+      //;
+      //gsi_replace (iter, g, false);
+
+      //stmt = gimple_build_assign (dest, src);
+      //gimple_set_location (stmt, loc);
+      //gimple_set_vuse (stmt, new_vuse);
+    }
+#endif
+
+  // FIXME: all we're doing so far is summing the sizes
+#if 0
+  gimple *last_acquire = m_alloc_pairs[m_alloc_pairs.length () - 1].m_acquire;
+
+  gimple_stmt_iterator last_gsi = gsi_for_stmt (last_acquire);
+  gsi_insert_seq_after (&last_gsi, seq, GSI_SAME_STMT);
+#endif
+#endif
+
+  FOR_EACH_VEC_ELT (m_alloc_pairs, i, pair)
+    pair->maybe_optimize ();
+}
+
+/* Comparator function for use by
+   allocation_group::sort_by_acquisition_order.  */
+
+static int
+sort_by_acquire_uid (const void *x, const void *y)
+{
+  const alloc_pair *tmp = (const alloc_pair *) x;
+  const alloc_pair *tmp2 = (const alloc_pair *) y;
+
+  return ((int)tmp->m_acquire->uid - (int)tmp2->m_acquire->uid);
+}
+
+/* Sort the alloc_pairs based on the ordering of the acquire stmts
+   (rather than the release stmts).  */
+
+void
+allocation_group::sort_by_acquisition_order ()
+{
+  /* Number the stmts within the BB. */
+  unsigned int idx = 0;
+  for (gimple_stmt_iterator gsi = gsi_start_bb (m_acquire_bb);
+       !gsi_end_p (gsi); gsi_next (&gsi))
+    {
+      gsi_stmt (gsi)->uid = idx;
+      idx++;
+    }
+
+  /* Sort the alloc_pairs based on the ordering of the alloc stmts.  */
+  m_alloc_pairs.qsort (sort_by_acquire_uid);
+}
+
+/* class function_state.  */
+
+/* Per-BB, look for deallocation calls; locate matching allocation calls, and
+   group them by the (alloc, dealloc) BB pairs.  */
+
+void
+function_state::find_heap_calls (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
+	{
+	  /* Iterate over statements, looking for function calls.  */
+	  gimple *stmt = gsi_stmt (si);
+
+	  if (gcall *call = dyn_cast <gcall *> (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_release (call, ALLOCATOR_FAMILY_NEW);
+		  if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)),
+				   "operator delete []"))
+		    handle_release (call, ALLOCATOR_FAMILY_VEC_NEW);
+		}
+
+	      //debug_tree (gimple_call_arg (stmt, 0));
+	      // FIXME: how to detect operator delete[] and operator delete ?
+	      // maybe with attributes?: tag the dealloc 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 (gimple_call_builtin_p (call, BUILT_IN_FREE))
+		handle_release (call, ALLOCATOR_FAMILY_MALLOC);
+	    }
+
+
+	  gsi_next (&si);
+	}
+    }
+}
+
+/* Use the information on heap calls discovered by
+   function_state::find_heap_calls, and attempt to optimize them.  */
+
+void
+function_state::optimize_heap_calls ()
+{
+  allocation_group *group;
+  unsigned i;
+  FOR_EACH_VEC_ELT (m_allocation_groups, i, group)
+    group->optimize_heap_calls ();
+}
+
+/* Determine if CALL is a known allocator function, and return its family.  */
+
+static enum allocator_family
+get_family_for_acquire_call (gcall *call)
+{
+  if (gimple_call_builtin_p (call, BUILT_IN_MALLOC))
+    return ALLOCATOR_FAMILY_MALLOC;
+
+  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_FAMILY_NEW;
+      if (0 == strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)),
+		       "operator new []"))
+	return ALLOCATOR_FAMILY_VEC_NEW;
+    }
+
+  /* Not recognized.  */
+  return ALLOCATOR_FAMILY_UNKNOWN;
+}
+
+/* Subroutine of lifetime_corresponds_to_control_flow_p.
+   Determine which basic block BB to use when checking that
+   all paths through BB reach a release call.
+   In the simple case this is the block of ACQUIRE_CALL,
+   but we can also detect error-handling of the form:
+
+     ptr = malloc (...);
+     if (ptr)
+
+   and
+
+     ptr = malloc (...);
+     if (!ptr)
+
+   and instead use the block reached when the allocation
+   is "successful", to cope with the fact that all release
+   calls treat release of NULL as a no-op.  */
+
+static basic_block
+get_effective_block_for_acquire (gcall *acquire_call)
+{
+  /* FIXME: for now, rely on the test being at the end of the same BB
+     as the acquire call.  */
+  gcond *last_cond = dyn_cast <gcond *> (last_stmt (acquire_call->bb));
+  if (!last_cond)
+    return acquire_call->bb;
+
+  /* Look for a comparison of the acquired ptr against NULL.  */
+  tree lhs = gimple_cond_lhs (last_cond);
+  if (lhs != gimple_call_lhs (acquire_call))
+    {
+      heap_remark (last_cond->location,
+		   "not error-handling: wrong left-hand-side");
+      return acquire_call->bb;
+    }
+  tree rhs = gimple_cond_rhs (last_cond);
+  if (!zerop (rhs))
+    {
+      heap_remark (last_cond->location,
+		   "not error-handling: not a comparison against 0");
+      return acquire_call->bb;
+    }
+
+  edge success_edge = NULL;
+  switch (gimple_cond_code (last_cond))
+    {
+    case EQ_EXPR:
+      /* if (ptr == NULL).  */
+      success_edge = get_false_edge_from_guard_bb (acquire_call->bb);
+      break;
+
+    case NE_EXPR:
+      /* if (ptr != NULL).  */
+      success_edge = get_true_edge_from_guard_bb (acquire_call->bb);
+      break;
+
+    default:
+      break;
+    }
+
+  if (success_edge)
+    {
+      heap_remark (last_cond->location, "detected error-handling");
+      return success_edge->dest;
+    }
+  else
+    {
+      heap_remark (last_cond->location,
+		   "could not detect basic block for successful allocation");
+      return acquire_call->bb;
+    }
+}
+
+/* To be able to optimize an acquire/release pair, every path through
+   the allocator call must also visit the deallocator call.
+   The converse must also nearly hold, but is made more complicated
+   by the need to cope with error-checking e.g.:
+
+     ptr = malloc (32);
+     if (ptr)
+       {
+	 do_stuff (ptr);
+	 free (ptr);
+       }
+
+   If there's an error-check, then we instead consider the BB
+   taken by the successful check as the effective location of the alloc
+   when considering post-dominance by the release call.  */
+
+static bool
+lifetime_corresponds_to_control_flow_p (gcall *acquire_call,
+					gcall *release_call)
+{
+  if (!dominated_by_p (CDI_DOMINATORS, release_call->bb, acquire_call->bb))
+    {
+      heap_remark (release_call->location,
+		   "can't optimize: deallocator block %i is not"
+		   " dominated by...",
+		   release_call->bb->index);
+      heap_remark (release_call->location,
+		   "...allocator block %i",
+		   acquire_call->bb->index);
+      return false;
+    }
+
+  basic_block effective_acquire_bb
+    = get_effective_block_for_acquire (acquire_call);
+
+  if (!dominated_by_p (CDI_POST_DOMINATORS, effective_acquire_bb,
+		       release_call->bb))
+    {
+      /* Tweak the message if error-handling was detected.  */
+      const char *gmsgid =
+	(effective_acquire_bb == acquire_call->bb
+	 ? G_("can't optimize: allocator block %i is not"
+	      " post-dominated by...")
+	 : G_("can't optimize: block %i after error-checking is not"
+	      " post-dominated by..."));
+      heap_remark (acquire_call->location,
+		   gmsgid, effective_acquire_bb->index);
+      heap_remark (release_call->location,
+		   "...deallocator block %i",
+		   release_call->bb->index);
+      return false;
+    }
+
+  return true;
+}
+
+/* Given RELEASE_CALL, a call to a known heap-deallocation function of
+   type FAMILY, attempt to locate the matching allocation call.  If
+   it is found, and the pair are sufficient sane to optimize, add the
+   pair to STATE.
+   Warn about mismatches (e.g. delete() of malloc()-ed memory).  */
+
+void
+function_state::handle_release (gcall *release_call,
+				enum allocator_family release_family)
+{
+  gcc_assert (release_family != ALLOCATOR_FAMILY_UNKNOWN);
+  heap_remark (release_call->location, "found deallocator");
+
+  tree ptr = gimple_call_arg (release_call, 0);
+  //warning_at (call->location, 0, "arg0 ptr: %qE", ptr);
+
+  /* Try to match the ptr up with an allocator call.  */
+  if (TREE_CODE (ptr) != SSA_NAME)
+    return;
+
+  gimple *def_stmt = SSA_NAME_DEF_STMT (ptr);
+  if (!def_stmt)
+    return;
+
+  gcall *acquire_call = dyn_cast <gcall *> (def_stmt);
+  if (!acquire_call)
+    {
+      heap_remark (release_call->location,
+		   "unable to locate matching allocation call");
+      return;
+    }
+
+  /* They must be of the same family; warn about mismatching
+     alloc/release calls.  */
+  enum allocator_family acquire_family
+    = get_family_for_acquire_call (acquire_call);
+
+  if (acquire_family == ALLOCATOR_FAMILY_UNKNOWN)
+    {
+      heap_remark (acquire_call->location,
+		   "unable to identify allocation call");
+      return;
+    }
+
+  if (acquire_family != release_family)
+    {
+      if (warning_at (release_call->location,
+		      OPT_Wmismatching_deallocator,
+		      "mismatching deallocator..."))
+	{
+	  inform (acquire_call->location, "...for allocation here");
+	  // FIXME: fix-it hint?
+	  /* FIXME: more test cases:
+	     - e.g. where it's unknown
+	     - e.g. for realloc.  */
+	}
+      return;
+    }
+
+  if (!lifetime_corresponds_to_control_flow_p (acquire_call, release_call))
+    return;
+
+  if (0)
+    warning_at (def_stmt->location, 0, "found matching pair");
+  add_alloc_pair (acquire_call, release_call, acquire_family);
+}
+
+/* Record an ACQUIRE/RELEASE as an allocation/deallocation pair
+   of the given FAMILY, for potential optimization.  */
+
+void
+function_state::add_alloc_pair (gcall *acquire, gcall *release,
+				enum allocator_family family)
+{
+  gcc_assert (acquire);
+  gcc_assert (release);
+
+  heap_remark (acquire->location, "alloc pair: acquire here...");
+  heap_remark (release->location, "alloc pair: ...release here");
+
+  allocation_group &group = get_allocation_group (acquire->bb, release->bb);
+  group.add_alloc_pair (acquire, release, family);
+}
+
+/* FIXME.  */
+
+allocation_group &
+function_state::get_allocation_group (basic_block bb_acquire,
+				      basic_block bb_release)
+{
+  /* FIXME: for now, we inefficiently do a linear search (giving O(n^2)
+     on the # of malloc calls in one function).  */
+
+  allocation_group *group;
+  unsigned i;
+  FOR_EACH_VEC_ELT (m_allocation_groups, i, group)
+    if (group->get_acquire_bb () == bb_acquire
+	&& group->get_release_bb () == bb_release)
+      return *group;
+
+  /* Not found?  Add one.  */
+  m_allocation_groups.safe_push (allocation_group (bb_acquire, bb_release));
+  return m_allocation_groups[m_allocation_groups.length () - 1];
+}
+
+/* class pass_heap_optimizations.  */
+
+/* Entry-point of the optimization pass.  */
+
+unsigned int
+pass_heap_optimizations::execute (function *fun)
+{
+  if (0)
+    heap_remark (fun->function_start_locus,
+		 "pass_heap_optimizations::execute: %qD", fun->decl);
+
+  // FIXME: maybe examine callgraph for calls to malloc and free?
+  // does this give us the BB?
+
+  //auto_vec<allocation> per_bb_allocs (n_basic_blocks_for_fn);
+
+  if (0)
+    {
+      push_cfun (fun);
+      dump_function_to_file (fun->decl, stderr, TDF_VOPS);
+      pop_cfun ();
+    }
+
+  if (0)
+    debug_alias_info ();
+
+  calculate_dominance_info (CDI_POST_DOMINATORS);
+  calculate_dominance_info (CDI_DOMINATORS);
+
+  function_state state;
+  state.find_heap_calls (fun);
+  state.optimize_heap_calls ();
+
+  optimize_alloca_calls (fun);
+
+  if (0)
+    {
+      push_cfun (fun);
+      dump_function_to_file (fun->decl, stderr, TDF_VOPS);
+      pop_cfun ();
+    }
+
+  free_dominance_info (CDI_DOMINATORS);
+  free_dominance_info (CDI_POST_DOMINATORS);
+
+  return 0; // FIXME
+}
+
+/* Attempt to optimize any alloca calls (both pre-existing ones, and any
+   created by this pass.  */
+
+void
+pass_heap_optimizations::optimize_alloca_calls (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); )
+	{
+	  /* Iterate over statements, looking for function calls.  */
+	  gimple *stmt = gsi_stmt (si);
+
+	  if (gimple_call_builtin_p (stmt, BUILT_IN_ALLOCA))
+	    maybe_optimize_alloca (as_a <gcall *> (stmt));
+
+	  gsi_next (&si);
+	}
+    }
+}
+
+/* Attempt to convert an alloca call to something else.
+
+   If we have:
+
+       ptr = (struct foo *)__builtin_alloca (sizeof (foo));
+
+   convert it to:
+
+       struct foo _obj;
+       ...
+       ptr = &_obj;
+
+   provided that it's safe to do so.
+   TODO: When is it safe?  */
+
+/* FIXME: potentially also convert this pattern:
+
+       ptr = T *__builtin_alloca (sizeof (T) * NUM);
+
+   to:
+
+       T _arr[NUM];
+       ...
+       ptr = T *__builtin_alloca (sizeof (T) * NUM);
+       ptr = _arr;.
+
+   assuming that NUM is constant.  */
+
+/* FIXME: what are the benefits of doing this?  */
+
+void
+pass_heap_optimizations::maybe_optimize_alloca (gcall *call)
+{
+  heap_remark (call->location, "considering alloca");
+  // TODO
+
+  tree size = gimple_call_arg (call, 0);
+  if (0)
+    debug_tree (size);
+
+  tree result = gimple_call_lhs (call);
+  if (0)
+    debug_tree (result);
+
+  /* Must be a fixed-size allocation.  */
+  if (TREE_CODE (size) != INTEGER_CST)
+    {
+      heap_remark (call->location, "can't optimize: not fixed size");
+      return;
+    }
+
+  /* FIXME.  */
+  if (TREE_CODE (TREE_TYPE (result)) != POINTER_TYPE)
+    {
+      heap_remark (call->location, "can't optimize: not a pointer type");
+      return;
+    }
+
+  tree obj_type = TREE_TYPE (TREE_TYPE (result));
+  if (0)
+    debug_tree (obj_type);
+
+  tree type_size = TYPE_SIZE (obj_type);
+
+  /* Must be of a known size.  */
+  if (!type_size)
+    {
+      heap_remark (call->location, "can't optimize: unknown size for type");
+      return;
+    }
+
+  /* Verify that size of allocation is sizeof (OBJ_TYPE).  */
+  //unsigned HOST_WIDE_INT obj_size = ; //tree_to_uhwi (DECL_SIZE_UNIT (obj_type));
+
+#if 0
+  tree obj_size = TREE_CODE (decl) == SSA_NAME
+    ? TYPE_SIZE_UNIT (TREE_TYPE (decl))
+    : DECL_SIZE_UNIT (decl);
+#endif
+
+#if 0
+  unsigned HOST_WIDE_INT alloca_size = tree_to_uhwi (size);
+  unsigned HOST_WIDE_INT obj_size = tree_to_uhwi (TYPE_SIZE (obj_type));
+#endif
+
+  /* FIXME: what other checks?  */
+
+  /* Must not be within a loop.  */
+  if (call->bb->loop_father->num)
+    {
+      heap_remark (call->location,
+		   "can't optimize: alloca is within a loop");
+      return;
+    }
+
+  // see also e.g. in_loop_p within gimple-ssa-warn-alloca.c
+
+  /* All checks passed.  */
+  heap_remark (call->location,
+	       "converting alloca to local: obj_size: %E bits",
+	       TYPE_SIZE (obj_type));
+
+  //tree new_local = make_ssa_name (obj_type);
+
+  gimple_seq seq = NULL;
+
+  // FIXME: reuse-lhs
+  //replace_call_with_call_and_fold (gimple_stmt_iterator *gsi, gimple *repl)
+
+  tree new_local = create_tmp_var (obj_type);
+  //tree lhs = make_ssa_name (TREE_TYPE (result));
+
+  tree addr_of_local = build1 (ADDR_EXPR, TREE_TYPE (result), new_local);
+  gimple *new_stmt = gimple_build_assign (result, addr_of_local);
+  //gimple_set_location (stmt, gimple_location (call));
+  //gimple_set_block (stmt, gimple_block (call));
+  gimple_seq_add_stmt (&seq, new_stmt);
+
+  gimple_stmt_iterator gsi = gsi_for_stmt (call);
+  gsi_insert_seq_after (&gsi, seq, GSI_SAME_STMT);
+
+  SSA_NAME_DEF_STMT (result) = new_stmt;
+
+  /* Remove the alloca call.  */
+#if 1
+  {
+    gimple_stmt_iterator gsi = gsi_for_stmt (call);
+    /* bool require_eh_edge_purge = */
+    gsi_remove (&gsi, false /* true*/);
+    unlink_stmt_vdef (call);
+    //release_defs (call);
+  }
+#endif
+
+
+  /* FIXME: add new temporary  */
+  /* Replace call and def_stmt with: ptr = &OBJ;.  */
+  /* Or just let dead-store elimination get it?  */
+}
+
+/* FIXME: */
+//} // anon namespace
+
+gimple_opt_pass *
+make_pass_heap_optimizations (gcc::context *ctxt)
+{
+  return new pass_heap_optimizations (ctxt);
+}
+
+/* FIXME:
+   Where should the pass live in passes.def?
+
+   Initially I put it after the first "pass_fre", so that pass_fre
+   could clean up things like:
+
+   <bb 21> [100.00%]:
+     this_65 = malloc (32);
+     [...do stuff with this_65...]
+     _87 = this_65;
+     ntog_41 = _87;
+     free (ntog_41);
+
+   into:
+
+   <bb 21> [100.00%]:
+     this_65 = malloc (32);
+     [...do stuff with this_65...]
+     free (this_65);
+
+   so that the SSA_NAMEs for the malloc/free match up and hence be
+   detected as an alloc_pair ("this_65" in this example; from the
+   objinst.c in PR 53082 after pass_early_inline
+   puts the malloc/free into the same function).
+
+   I then moved it to after the first "pass_cd_dce"; pass_cd_dce
+   can eliminate redundant malloc/free calls, so I belive my thinking
+   was to work on anything left behind by it.
+
+   When experimenting with std::unique_ptr I noticed that with:
+
+     void test_unique_ptr (void)
+     {
+       std::unique_ptr<foo> p (new foo);
+     }
+
+   we get (after early_inline and evrp):
+
+   <bb 2> [100.00%]:
+     _3 = operator new (1);
+     foo::foo (_3);
+     std::default_delete<foo>::_ZNKSt14default_deleteI3fooEclEPS0_.isra.0 (_3);
+     return;
+
+   which fails to recognize the alloc/dealloc pair.  To do so,
+   we need the full inliner to run, so that the use
+   of std::default_delete<foo> in the above is converted:
+
+   <bb 3> [57.44%]:
+     foo::~foo (_3);
+     operator delete (_3, 1);
+
+   from which the alloc_pair can be found (with -fno-exceptions).
+   Hence it seems useful to run the pass after the "pass_ipa_inline".
+   I moved it to after the third "pass_fre": this is after the inliner
+   and given the use of the first "pass_fre" above, I cargo-culted using
+   it again.
+
+   When experimenting with pr42958-tonto.f90 I noticed that the size
+   calculation is loop-invariant, and the malloc/free could potentially
+   be hoisted out of the loop.  I tried moving it to after pass_lim to take
+   advantage of hoisting.  The size calculation does indeed get hoisted out
+   of the loop for that test case, but "lim" happens after "dom", and "dom"
+   was duplicating the allocation call for this test case, which prevents
+   the alloc_pair detection from working.  */
diff --git a/gcc/params.def b/gcc/params.def
index 1b058e4..8d4908d 100644
--- a/gcc/params.def
+++ b/gcc/params.def
@@ -684,6 +684,11 @@ DEFPARAM(PARAM_MAX_COMBINE_INSNS,
 	 "The maximum number of insns combine tries to combine.",
 	 4, 2, 4)
 
+DEFPARAM(PARAM_MAX_ALLOCA_OPTIMIZATION_SIZE,
+	 "max-alloca-optimization-size",
+	 "The maximum size of allocation for -fheap-optimizations to convert to an alloca.",
+	 64, 0, 0)
+
 /* INTEGER_CST nodes are shared for values [{-1,0} .. N) for
    {signed,unsigned} integral types.  This determines N.
    Experimentation shows 251 to be a good value that generates the
diff --git a/gcc/passes.def b/gcc/passes.def
index 6b0f05b..692881a 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -93,6 +93,7 @@ along with GCC; see the file COPYING3.  If not see
 	     execute TODO_rebuild_alias at this point.  */
 	  NEXT_PASS (pass_build_ealias);
 	  NEXT_PASS (pass_fre);
+
 	  NEXT_PASS (pass_early_vrp);
 	  NEXT_PASS (pass_merge_phi);
           NEXT_PASS (pass_dse);
@@ -205,6 +206,12 @@ along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_build_alias);
       NEXT_PASS (pass_return_slot);
       NEXT_PASS (pass_fre);
+
+      /* FIXME:
+	 Where should this pass live?  See the notes at the end of
+	 heap-optimizations.c.  */
+      NEXT_PASS (pass_heap_optimizations);
+
       NEXT_PASS (pass_merge_phi);
       NEXT_PASS (pass_thread_jumps);
       NEXT_PASS (pass_vrp, true /* warn_array_bounds_p */);
diff --git a/gcc/testsuite/c-c++-common/heap-optimization.c b/gcc/testsuite/c-c++-common/heap-optimization.c
new file mode 100644
index 0000000..37efa918
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/heap-optimization.c
@@ -0,0 +1,1029 @@
+/* { dg-do compile } */
+/* { dg-options "-fheap-optimizations -fremark-heap-optimizations" }  */
+
+#include <stdlib.h>
+
+extern int some_condition (void);
+extern void do_something (void *ptr);
+
+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)
+{
+  edit_distance_t *v0, *v1;
+  edit_distance_t result;
+  int i;
+  int j;
+
+  if (len_s == 0)
+    return len_t;
+  if (len_t == 0)
+    return len_s;
+
+  v0 = malloc(sizeof (edit_distance_t) * (len_s + 1));
+  v1 = malloc(sizeof (edit_distance_t) * (len_s + 1));
+
+  for (i = 0; i < len_s + 1; i++)
+    v0[i] = i;
+
+  for (i = 0; i < len_t; i++)
+    {
+      v1[0] = i + 1;
+
+      for (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 (j = 0; j < len_s + 1; j++)
+	v0[j] = v1[j];
+    }
+
+  result = v1[len_s];
+  free(v0);
+  free(v1);
+  return result;
+}
+
+/* This already gets optimized well.
+
+   fre1 turns:
+     result = *ptr_a + *ptr_b;
+   into:
+     result = x + y;
+
+   Then dse1 eliminates the "*ptr_a = x;" and for b and y.
+
+   Then cddce1 eliminates the malloc/free, leading to just:
+     result = x + y;
+ */
+
+int unboxing (int x, int y)
+{
+  int *ptr_a, *ptr_b;
+  int result;
+
+  ptr_a = malloc (sizeof(int));
+  *ptr_a = x;
+
+  ptr_b = malloc (sizeof(int));
+  *ptr_b = y;
+
+  result = *ptr_a + *ptr_b;
+
+  free (ptr_a);
+  free (ptr_b);
+
+  return result;
+}
+
+/* As above, but for alloca, rather than malloc; optimized in same way.  */
+
+int unboxing_alloca (int x, int y)
+{
+  int *ptr_a, *ptr_b;
+  int result;
+
+  ptr_a = alloca (sizeof(int));
+  *ptr_a = x;
+
+  ptr_b = alloca (sizeof(int));
+  *ptr_b = y;
+
+  result = *ptr_a + *ptr_b;
+
+  return result;
+}
+
+/* As above, but with a struct wrapper; optimized in the same way.  */
+
+struct boxed_int
+{
+  int value;
+};
+
+int unboxing_struct (int x, int y)
+{
+  struct boxed_int *ptr_a, *ptr_b;
+  int result;
+
+  ptr_a = alloca (sizeof (struct boxed_int));
+  ptr_a->value = x;
+  ptr_b = alloca (sizeof (struct boxed_int));
+  ptr_b->value = y;
+
+  result = ptr_a->value + ptr_b->value;
+
+  return result;
+}
+
+/* PR tree-optimization/21046.  */
+
+extern void foo (char *p, int i);
+extern void bar (char *p, int i, int j);
+
+void hoist_alloc_1 (void)
+{
+  char *p;
+  int i;
+
+  for (i = 0; i < 10; i++)
+    {
+      p = malloc (20);
+      foo (p, i);
+      free (p);
+    }
+}
+
+void hoist_alloc_2 (void)
+{
+  char *p;
+  int i;
+
+  for (i = 0; i < 10; i++)
+    {
+      p = malloc (2 * i + 2);
+      foo (p, i);
+      free (p);
+    }
+}
+
+void hoist_alloc_3 (void)
+{
+  char *p;
+  int i, j;
+
+  for (i = 0; i < 10; i++)
+    for (j = 0; j < 10; j++)
+      {
+	p = malloc (20);
+	bar (p, i, j);
+	free (p);
+      }
+}
+
+void hoist_alloc_4 (void)
+{
+  char *p;
+  int i, j;
+
+  for (i = 0; i < 10; i++)
+    for (j = 0; j < 10; j++)
+      {
+	p = malloc (2 * i * j);
+	bar (p, i, j);
+	free (p);
+      }
+}
+
+void hoist_alloc_5 (void)
+{
+  char *p;
+  int i, j;
+
+  for (i = 0; i < 10; i++)
+    for (j = 0; j < 10; j++)
+      {
+	/* The size of this allocation is invariant
+	   within the inner loop, but not the outer loop.  */
+	p = malloc (2 * i);
+	bar (p, i, j);
+	free (p);
+      }
+}
+
+void hoist_alloc_6 (int n)
+{
+  char *p;
+  int i, j;
+
+  for (i = 0; i < 10; i++)
+    for (j = 0; j < 10; j++)
+      {
+	p = malloc (n * i * j);
+	bar (p, i, j);
+	free (p);
+      }
+}
+
+/* PR middle-end/53082.  */
+
+void test_redundant_1 (void)
+{
+  int i;
+  for (i = 0; i < 100; i++)
+    {
+      int *s = (int*) malloc (sizeof(int));
+      *s = 10;
+      free (s);
+    }
+}
+
+void test_combined (void)
+{
+  int i;
+  for (i = 0; i < 100; i++)
+    {
+      int *s1 = malloc (sizeof(int));
+      int *s2 = malloc (sizeof(int));
+      int *s3 = malloc (sizeof(int));
+      do_something (s1);
+      do_something (s2);
+      do_something (s3);
+      free(s1); free(s2); free(s3);
+    }
+}
+
+// objinst.c
+
+/* -*- mode: c -*-
+ * $Id: objinst.c 36673 2007-05-03 16:55:46Z laurov $
+ * http://www.bagley.org/~doug/shootout/
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+enum {false, true};
+
+#define TOGGLE \
+    char state; \
+    char (*value)(struct Toggle *); \
+    struct Toggle *(*activate)(struct Toggle *)
+
+#define DESTROY  free
+
+typedef struct Toggle {
+    TOGGLE;
+} Toggle;
+
+char toggle_value(Toggle *this) {
+    return(this->state);
+}
+Toggle *toggle_activate(Toggle *this) {
+    this->state = !this->state;
+    return(this);
+}
+Toggle *init_Toggle(Toggle *this, char start_state) {
+    this->state = start_state;
+    this->value = toggle_value;
+    this->activate = toggle_activate;
+    return(this);
+}
+Toggle *new_Toggle(char start_state) {
+    Toggle *this = (Toggle *)malloc(sizeof(Toggle));
+    return(init_Toggle(this, start_state));
+}
+
+
+typedef struct NthToggle {
+    Toggle base;
+    int count_max;
+    int counter;
+} NthToggle;
+
+NthToggle *nth_toggle_activate(NthToggle *this) {
+    if (++this->counter >= this->count_max) {
+        this->base.state = !this->base.state;
+        this->counter = 0;
+    }
+    return(this);
+
+}
+NthToggle *init_NthToggle(NthToggle *this, int max_count) {
+    this->count_max = max_count;
+    this->counter = 0;
+    this->base.activate = (Toggle *(*)(Toggle *))nth_toggle_activate;
+    return(this);
+}
+NthToggle *new_NthToggle(char start_state, int max_count) {
+    NthToggle *this = (NthToggle *)malloc(sizeof(NthToggle));
+    this = (NthToggle *)init_Toggle((Toggle *)this, start_state);
+    return(init_NthToggle(this, max_count));
+}
+
+
+int main(int argc, char *argv[]) {
+#ifdef SMALL_PROBLEM_SIZE
+#define LENGTH 7000000
+#else
+#define LENGTH 70000000
+#endif
+    int i, n = ((argc == 2) ? atoi(argv[1]) : LENGTH);
+    Toggle *tog;
+    NthToggle *ntog;
+
+    tog = new_Toggle(true);
+    for (i=0; i<5; i++) {
+        puts((tog->activate(tog)->value(tog)) ? "true" : "false");
+    }
+    DESTROY(tog);
+    for (i=0; i<n; i++) {
+        tog = new_Toggle(true);
+        DESTROY(tog);
+    }
+    
+    puts("");
+
+    ntog = new_NthToggle(true, 3);
+    for (i=0; i<8; i++) {
+        const char *Msg;
+        if (ntog->base.activate((Toggle*)ntog)->value((Toggle*)ntog))
+          Msg = "true";
+        else
+          Msg = "false";
+        puts(Msg);
+    }
+    DESTROY(ntog);
+    for (i=0; i<n; i++) {
+        ntog = new_NthToggle(true, 3);
+        DESTROY(ntog);
+    }
+    return 0;
+}
+
+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 = malloc (sizeof (value));
+  v->kind = KIND_INT;
+  v->u.u_int = i;
+  return v;
+}
+
+value *value_new_float (float f)
+{
+  value *v = malloc (sizeof (value));
+  v->kind = KIND_FLOAT;
+  v->u.u_float = f;
+  return v;
+}
+
+void value_free (value *v)
+{
+  free (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;
+}
+
+void void_ptr (int sz)
+{
+  void *ptr = malloc (64);
+  free (ptr);
+}
+
+struct opaque;
+
+void opaque_ptr (int sz)
+{
+  struct opaque *ptr = (struct opaque *)malloc (64);
+  free (ptr);
+}
+
+void *conditional_free (int freeit)
+{
+  void *ptr = malloc (64);
+  if (freeit)
+    {
+      /* We shouldn't convert this pair to alloca.  */
+      free (ptr);
+      return NULL;
+    }
+
+  return ptr;  
+}
+
+void conditional_malloc_1 (void *ptr, int mallocit)
+{
+  if (mallocit)
+    ptr = malloc (64);
+
+  free (ptr);
+}
+
+void conditional_malloc_2 (void *ptr, int mallocit)
+{
+  if (mallocit)
+    ptr = malloc (64);
+
+  do_something (ptr);
+
+  if (mallocit)
+    free (ptr);
+}
+
+void conditional_malloc_3 (void *ptr, int mallocit)
+{
+  if (mallocit)
+    ptr = malloc (64);
+  else
+    ptr = malloc (32);
+
+  do_something (ptr);
+
+  free (ptr);
+}
+
+void too_large_for_alloca (void)
+{
+  void *ptr = malloc (1024);
+  do_something (ptr);
+  free (ptr);
+}
+
+void with_error_checking_1 (void)
+{
+  void *ptr = malloc (32);
+  if (ptr)
+    do_something (ptr);
+  free (ptr);
+}
+
+/* FIXME: currently we don't optimize this one,
+   since the allocator block is not post-dominated by the
+   deallocator block.
+   To handle this, we could eliminate unnecessary checks on free's
+   input being non-NULL, or we could make the path logic smarter.
+
+   This also applies to operator delete/delete[], which are also required to
+   handle NULL ptrs as a no-op.  */
+   
+void with_error_checking_2 (void)
+{
+  void *ptr = malloc (32);
+  if (ptr)
+    {
+      do_something (ptr);
+      free (ptr);
+    }
+}
+
+/* It can optimize this one: free block postdominates malloc block.  */
+
+void with_error_checking_3 (void)
+{
+  void *ptr = malloc (32);
+  if (!ptr)
+    goto err;
+
+  do_something (ptr);
+ err:
+  free (ptr);
+}
+
+/* FIXME: it can't optimize this one, for similar reasons to
+   with_error_checking_2 above.  */
+
+void with_error_checking_4 (void)
+{
+  void *ptr = malloc (32);
+  if (!ptr)
+    goto err;
+
+  do_something (ptr);
+  free (ptr);
+
+ err:
+  return;
+}
+
+/* FIXME: it can't optimize this one, for similar reasons to
+   with_error_checking_2 above.  */
+
+void with_error_checking_5 (void)
+{
+  void *ptr = malloc (32);
+  if (!ptr)
+    return;
+
+  do_something (ptr);
+  free (ptr);
+}
+
+void with_error_checking_6 (void *other_ptr)
+{
+  void *ptr = malloc (32);
+  /* Not a comparison with ptr; don't optimize.  */
+  if (other_ptr)
+    return;
+
+  do_something (ptr);
+  free (ptr);
+}
+
+void with_error_checking_7 (void)
+{
+  void *ptr = malloc (32);
+  /* Not a comparison against NULL; don't optimize.  */
+  if (ptr == (void *)0x42)
+    return;
+
+  do_something (ptr);
+  free (ptr);
+}
+
+void with_error_checking_8 (void)
+{
+  void *ptr = malloc (32);
+  /* Wrong king of comparison; don't optimize.  */
+  if (ptr > (void *)0x42)
+    return;
+
+  do_something (ptr);
+  free (ptr);
+}
+
+void with_error_checking_9 (void)
+{
+  void *ptr_a = malloc (32);
+  void *ptr_b = malloc (32);
+
+  if (ptr_a && ptr_b)
+    {
+      do_something (ptr_a);
+      do_something (ptr_b);
+    }
+
+  free (ptr_b);
+  free (ptr_a);
+}
+
+void with_error_checking_9a (void)
+{
+  void *ptr_a = malloc (32);
+  void *ptr_b = malloc (32);
+
+  if (ptr_a)
+    if (ptr_b)
+      {
+	do_something (ptr_a);
+	do_something (ptr_b);
+      }
+
+  free (ptr_b);
+  free (ptr_a);
+}
+
+void with_error_checking_10 (void)
+{
+  void *ptr_a = NULL;
+  void *ptr_b = NULL;
+  ptr_a = malloc (32);
+  if (!ptr_a)
+    goto err;
+
+  /* FIXME: it doesn't seem to detect the 2nd one here.  */
+  ptr_b = malloc (32);
+  if (!ptr_b)
+    goto err;
+  
+  do_something (ptr_a);
+  do_something (ptr_b);
+
+ err:
+  free (ptr_b);
+  free (ptr_a);
+}
+
+void with_error_checking_11 (void)
+{
+  void *ptr_a = malloc (32);
+  if (ptr_a)
+    {
+      void *ptr_b = malloc (32);
+      if (ptr_b)
+	{
+	  do_something (ptr_a);
+	  do_something (ptr_b);
+
+	  free (ptr_b);
+	}
+      free (ptr_a);
+    }
+}
+
+/* FIXME: similar tests for C++, with and without exception-handling.  */
+
+void test_alloc_size (void)
+{
+  char *p = malloc (20);
+  do_something (p);
+  free (p);
+}
+
+/* FIXME.  */
+
+void test_calloc (void)
+{
+  void *p = calloc (1, 20);
+  do_something (p);
+  free (p);
+}
+
+/* FIXME.  */
+
+/* This should not be optimized, due to the ptr escaping.  */
+
+void *global;
+
+void test_escaping_write_to_global (void)
+{
+  void *p = malloc (20);
+
+  global = p;
+
+  free (p);
+}
+
+/* This should not be optimized, due to the ptr escaping.  */
+
+extern void some_extern_fn (void *);
+
+void test_escaping_call_to_extern_fn (void)
+{
+  void *p = malloc (20);
+
+  some_extern_fn (p);
+
+  free (p);
+}
+
+/* This should be optimized, due to the ptr being const.  */
+
+extern void const_extern_fn (const void *);
+
+void test_call_to_const_extern_fn (void)
+{
+  void *p = malloc (20);
+
+  /* some inline initialization of p.  */
+
+  const_extern_fn (p);
+
+  free (p);
+}
+
+void test_alloc_loop (void)
+{
+  int i;
+  void *ptr;
+
+  ptr = malloc (20);
+  for (i = 0; i < 10; i++)
+    free (ptr);
+}
+
+/* FIXME: this is erroneously converted to an obstack alloc/dealloc
+   which may convert a memory leak into a crash.  */
+
+void test_dealloc_loop (void)
+{
+  int i;
+  void *ptr;
+
+  for (int i = 0; i < 10; i++)
+    ptr = malloc (20);
+  free (ptr);
+}
+
+/* FIXME: realloc */
+
+void test_realloc_1 (void)
+{
+  /* This one is missing error checking and hence leaks if the realloc fails.  */
+  void *ptr = malloc (32);
+  ptr = realloc (ptr, 64);
+  free (ptr);
+}
+
+void test_realloc_2 (void)
+{
+  void *ptr = malloc (32);
+  void *new_ptr = realloc (ptr, 64);
+  if (!new_ptr)
+    {
+      free(ptr);
+      return;
+    }
+
+  free (new_ptr);
+}
+
+void test_realloc_3 (void)
+{
+   void *p = malloc (32);
+
+   void *q = realloc (p, 64);
+
+   if (p == q)
+     free (p);
+   else
+     free (q);
+}
+
+void test_realloc_4 (void)
+{
+   void *p = malloc (32);
+
+   realloc (p, 64);
+
+   free (p);
+}
+
+/* alloca tests.  */
+
+typedef struct point
+{
+  float x, y;
+} point;
+
+int test_alloca_1 (void)
+{
+  char *ch_ptr = alloca (32);
+  int sum;
+
+  for (int i = 0; i < 32; i++)
+    ch_ptr[i] = 'a' + i;
+  for (int i = 0; i < 32; i++)
+    sum += ch_ptr[i];
+
+  return sum;  
+}
+
+int test_alloca_2 (void)
+{
+  void *v_ptr = alloca (32);
+  char *ch_ptr = v_ptr;
+  int sum;
+
+  for (int i = 0; i < 32; i++)
+    ch_ptr[i] = 'a' + i;
+  for (int i = 0; i < 32; i++)
+    sum += ch_ptr[i];
+
+  return sum;  
+}
+
+/* Example of alloca in a loop, where the alloca results
+   have observably different values.  */
+
+int test_alloca_3 (void)
+{
+  char *ptrs[32];
+  int sum;
+
+  for (int i = 0; i < 32; i++)
+    {
+      char *ptr = alloca (1);
+      ptrs[i] = ptr;
+    }
+
+  for (int i = 0; i < 32; i++)
+    *ptrs[i] = 'a' + i;
+
+  for (int i = 0; i < 32; i++)
+    sum += *ptrs[i];
+
+  return sum;
+}
+
+void *test_alloca_4 (float x, float y)
+{
+  /* Ought to be able to convert this to an array.  */
+  point *coord = alloca (sizeof (point));
+  return coord;
+}
+
+float test_alloca_5 (float *x, float *y)
+{
+  float sum = 0.f;
+  /* Ought to be able to convert this to an array.  */
+  point *coords = alloca (sizeof (point) * 16);
+  for (int i = 0; i < 16; i++)
+    {
+      coords[i].x = x[i];
+      coords[i].y = y[i];
+    }
+  for (int i = 0; i < 16; i++)
+    sum += coords[i].x * coords[i].y;
+  return sum;
+}
+
+/* More flow-control coverage.  */
+
+void test_alternate_1 (int flag)
+{
+  if (flag)
+    {
+      void *p = malloc (32);
+      do_something (p);
+      free (p);
+    }
+  else
+    {
+      void *q = malloc (64);
+      do_something (q);
+      free (q);
+    }
+}
+
+void test_nested_1 (int flag)
+{
+  void *p;
+  void *q;
+
+  p = malloc (32);
+  do_something (p);
+
+  if (flag)
+    {
+      q = malloc (32);
+      do_something (q);
+      free (p);
+      free (q);
+    }
+  else
+    {
+      free (p);
+    }
+}
+
+void test_non_nested_1 (int flag)
+{
+  void *p;
+  void *q;
+
+  p = malloc (32);
+  do_something (p);
+
+  if (flag)
+    {
+      q = malloc (32);
+      do_something (q);
+      free (p);
+    }
+  else
+    {
+      free (p);
+    }
+
+  if (flag)
+    free (q);
+}
+
+/* alloca inside an ireducible flowgraph, where
+   the alloca can be run an arbitrary number of times.  */
+/* FIXME: this isn't detected as a "loop", so currently the pass
+   erroneously optimizes this.  */
+
+void test_two_entry_loop (void)
+{
+  int *ptr;
+
+  if (some_condition ())
+    goto b2;
+  else
+    goto b3;
+
+ b2:
+  ptr = alloca (sizeof(int));
+  do_something (ptr);
+  if (some_condition ())
+    goto b3;
+  else
+    goto b4;
+
+ b3:
+  ptr = alloca (sizeof(int));
+  do_something (ptr);
+  if (some_condition ())
+    goto b2;
+  else
+    goto b5;
+
+ b4:
+  do_something (ptr);
+  return;
+
+ b5:
+  return;
+}
diff --git a/gcc/testsuite/c-c++-common/obstack-builtins.c b/gcc/testsuite/c-c++-common/obstack-builtins.c
new file mode 100644
index 0000000..de10707
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/obstack-builtins.c
@@ -0,0 +1,181 @@
+/* { dg-do run } */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if 1
+
+#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
+
+typedef struct chunk
+{
+  void *m_buffer;
+  size_t m_sz;
+  struct chunk *m_next;
+  struct chunk *m_prev;
+  size_t m_offset;
+} chunk;
+
+typedef struct obstack_tls
+{
+  chunk *m_last;
+  chunk *m_current;
+  size_t m_min_buf_size;
+} obstack_tls;
+
+static __thread obstack_tls the_tls;
+
+static int
+chunk_has_capacity_p (chunk *ch, size_t sz)
+{
+  return ch->m_offset + sz < ch->m_sz;
+}
+
+/* Allocate SZ bytes within CH.
+   SZ must be a multiple of 8 (to preserve alignment).  */
+
+static void *
+chunk_allocate (chunk *ch, size_t sz)
+{
+  void *result;
+
+  /* FIXME: alignment?  */
+  assert (chunk_has_capacity_p (ch, sz));
+
+  result = ch->m_buffer + ch->m_offset;
+  ch->m_offset += sz;
+
+  return result;
+}
+
+/* Allocate a new chunk big enough to hold MIN_SIZE.
+   Set m_buffer and m_sz.
+   Do not set m_next and m_prev.  */
+
+static chunk *
+obstack_tls_new_chunk (obstack_tls *tls, size_t min_size)
+{
+  chunk *ch = (chunk *)calloc (1, sizeof (chunk));
+  if (!ch)
+    return NULL;
+  size_t buf_size = MAX (min_size, tls->m_min_buf_size);
+  ch->m_sz = buf_size;
+  ch->m_buffer = malloc (buf_size);
+  if (!ch->m_buffer)
+    {
+      free (ch);
+      return NULL;
+    }
+  return ch;  
+}
+
+static void *
+obstack_tls_alloc (obstack_tls *tls, size_t sz)
+{
+  /* Round size up to next multiple of 8 to ensure
+     8-byte alignment.  */
+  sz = (sz / 8 ) * 8;
+
+  if (!tls->m_current) /* FIXME: mark as unlikely?  */
+    {
+      /* First allocation on this thread.  */
+      assert (tls->m_last == NULL);
+      assert (tls->m_min_buf_size == 0);
+      tls->m_min_buf_size = 1024 * 64;
+      tls->m_current = tls->m_last = obstack_tls_new_chunk (tls, sz);
+      return chunk_allocate (tls->m_current, sz);
+    }
+  assert (tls->m_current);
+
+  while (tls->m_current)
+    if (chunk_has_capacity_p (tls->m_current, sz))
+      return chunk_allocate (tls->m_current, sz);
+    else
+      tls->m_current = tls->m_current->m_next;
+
+  /* Otherwise, allocate a new chunk.  */
+  chunk *ch = obstack_tls_new_chunk (tls, sz);
+  ch->m_prev = tls->m_last;
+  tls->m_last->m_next = ch;
+  tls->m_last = ch;
+  tls->m_current = ch;
+
+  // FIXME: is this really efficient?
+  // FIXME: maybe serve large allocations out of malloc?
+  // (in which case we need to know the size when freeing)
+
+  return chunk_allocate (tls->m_current, sz);
+}
+
+static void *
+obstack_tls_dealloc (obstack_tls *tls, void *ptr)
+{
+  // TODO: probably much easier if we know the size
+}
+
+void *
+__builtin_obstack_alloc (size_t sz)
+{
+  return obstack_tls_alloc (&the_tls, sz);
+}
+
+void
+__builtin_obstack_dealloc (void *ptr)
+{
+  obstack_tls_dealloc (&the_tls, ptr);
+}
+
+#else
+
+/* Pass-through implementation in terms of malloc/free.  */
+
+void *
+__builtin_obstack_alloc (size_t sz)
+{
+  return malloc (sz);
+}
+
+void
+__builtin_obstack_dealloc (void *ptr)
+{
+  free (ptr);
+}
+
+#endif
+
+/* Tests.  */
+// TODO: multithreaded tests
+
+static void
+test_1 (void)
+{
+  void *ptr = __builtin_obstack_alloc (64);
+  fprintf (stderr, "ptr: %p\n", ptr);
+  __builtin_obstack_dealloc (ptr);
+}
+
+static void
+test_2 (int num_allocs, size_t sz)
+{
+  int i;
+  void **ptrs = (void **)calloc (num_allocs, sizeof (void *));
+
+  for (i = 0; i < num_allocs; i++)
+    {
+      ptrs[i] = __builtin_obstack_alloc (sz);
+      fprintf (stderr, "ptrs[%i]: %p\n", i, ptrs[i]);
+    }
+
+  for (i = num_allocs; i > 0; i--)
+    __builtin_obstack_dealloc (ptrs[i - 1]);
+
+  free (ptrs);
+}
+
+int main (int argc, const char *argv)
+{
+  test_1 ();
+  test_2 (150, 1024);
+  return 0;
+}
diff --git a/gcc/testsuite/g++.dg/heap-idioms.C b/gcc/testsuite/g++.dg/heap-idioms.C
new file mode 100644
index 0000000..51ee894
--- /dev/null
+++ b/gcc/testsuite/g++.dg/heap-idioms.C
@@ -0,0 +1,54 @@
+#include <memory>
+
+class foo
+{
+public:
+  foo ();
+  ~foo ();
+
+private:
+  char m_dummy[32];
+};
+
+/* std::unique_ptr.  */
+
+void test_unique_ptr (void)
+{
+  std::unique_ptr<foo> p (new foo);
+}
+
+/* 029t.einline inlines the ctor/dtor, adding:
+   std::default_delete<foo>::_ZNKSt14default_deleteI3fooEclEPS0_.isra.0 (_8);
+   to the function body.
+
+   038t.evrp eliminates the control flow and turns it into:
+
+    <bb 2> [100.00%]:
+      _3 = operator new (1);
+      foo::foo (_3);
+      std::default_delete<foo>::_ZNKSt14default_deleteI3fooEclEPS0_.isra.0 (_3);
+      return;
+
+   079i.inline lines the std::default_delete<foo>, and turns it into;
+
+     <bb 3> [57.44%]:
+      foo::~foo (_3);
+      operator delete (_3, 1);
+
+   from which the alloc_pair can be found (with -fno-exceptions),
+   assuming that the heap pass runs after the inliner.  */
+
+/* FIXME: however, it's treated as a void * and hence not converted to a local.
+   Can we type-promote it, or make the convert-alloca-to-local a bit
+   smarter?  */
+
+#if 0
+
+/* std::shared_ptr.  */
+// FIXME: this generates a nasty mess that doesn't look optimizable by "heap"
+
+void test_shared_ptr (void)
+{
+  std::shared_ptr<foo> p = std::make_shared<foo>();
+}
+#endif
diff --git a/gcc/testsuite/g++.dg/heap-optimization.C b/gcc/testsuite/g++.dg/heap-optimization.C
new file mode 100644
index 0000000..96734553
--- /dev/null
+++ b/gcc/testsuite/g++.dg/heap-optimization.C
@@ -0,0 +1,288 @@
+/* { dg-do compile } */
+/* { dg-options "-fheap-optimizations -fremark-heap-optimizations" }  */
+
+#include <stdlib.h>
+
+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;
+
+  int *ptr2 = new int[10];
+  delete(ptr2);
+}
+
+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;
+  value_free (d);
+  value_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;
+}
diff --git a/gcc/testsuite/g++.dg/heap-with-exceptions.C b/gcc/testsuite/g++.dg/heap-with-exceptions.C
new file mode 100644
index 0000000..c7523d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/heap-with-exceptions.C
@@ -0,0 +1,144 @@
+/* FIXME: -fheap-optimizations does a reasonable job on this file with
+   -fno-exceptions, but is stymied by the default of -fexceptions.  */
+
+class foo
+{
+public:
+  foo ();
+  ~foo ();
+
+  void do_stuff ();
+
+private:
+  char m_dummy[64];
+};
+
+/**************************************************************************/
+// Tests of "naked" new/delete
+
+void naked_new_test_1 (void)
+{
+  foo *f = new foo;
+  delete f;
+}
+
+void naked_new_test_2 (void)
+{
+  foo *f = new foo;
+  f->do_stuff ();
+  delete f;
+}
+
+/* This isn't exception safe, and will leak f if g fails.  */
+
+void naked_new_test_3 (void)
+{
+  foo *f = new foo;
+  foo *g = new foo;
+
+  delete g;
+  delete f;
+}
+
+void naked_new_test_4 (void)
+{
+  foo *arr = new foo[16];
+  delete[] arr;
+}
+
+/**************************************************************************/
+// Tests of an "owning" wrapper ptr
+
+/* A wrapper for a foo *.  */
+class foo_ptr
+{
+public:
+  foo_ptr () { m_ptr = new foo (); }
+  ~foo_ptr () { delete m_ptr; }
+
+private:
+  foo *m_ptr;
+};
+
+/* Inlining of the foo_ptr ctor and dtor in these functions happens
+   within "079i.inline", though confusingly, 080i.pure-const and a
+   few others seem to be happening *before* 079i.inline.  */
+
+void ownership_test_1 (void)
+{
+  foo_ptr f;
+}
+
+void ownership_test_2 (void)
+{
+  foo_ptr f;
+  foo_ptr g;
+}
+
+/**************************************************************************/
+// Tests of an refcounted ptr
+
+class counted
+{
+public:
+  counted () : m_refcnt (0) {}
+  //virtual ~counted () {}
+  void incref () { m_refcnt++; }
+  void decref () { if (--m_refcnt == 0) delete this; }
+
+private:
+  int m_refcnt;
+};
+
+class refcount_ptr
+{
+public:
+  refcount_ptr (counted *obj) : m_ptr (obj) { if (m_ptr) m_ptr->incref (); }
+  refcount_ptr (const refcount_ptr &other)
+    : m_ptr (other.m_ptr) { if (m_ptr) m_ptr->incref (); }
+  ~refcount_ptr () { if (m_ptr) m_ptr->decref (); }
+
+private:
+  counted *m_ptr;
+};
+
+/* With no dtor:
+   It's at 046t.ehcleanup1 that the "operator new" failure edge is removed.
+   It's at 079i.inline that the refcount_ptr code gets inlined, and
+   the delete appears.
+   At 104t.vrp1 refcount_test_1 becomes just a single BB (apart from ENTRY/EXIT).
+   At 228t.optimize we have a matching operator new/delete pair in one BB,,
+   with an assign of zero to the refcount, for both functions.  */
+
+/* With virtual dtor:
+   029t.einline inlines the refcount_ptr code, but the "operator delete"
+   doesn't appear (is it on an edge??)
+   037t.fre1 devirts the vtable dtor jump into a call to ~counted.
+   The "operator delete" call appears in 087t.fixup_cfg4
+   (was it on an edge?).
+   At 122t.dse2 we have just a matching operator new/delete pair in one BB,
+   for both functions.  */
+
+// i.e. it does better with the virtual dtor
+
+void refcount_test_1 ()
+{
+  refcount_ptr p (new counted);
+}
+
+/* With no dtor, at 119t.dom2 refcount_test_2 becomes just a
+   single BB (apart from ENTRY/EXIT).  */
+
+void refcount_test_2 ()
+{
+  refcount_ptr p (new counted);
+  refcount_ptr q = p;
+}
+
+/* FIXME.  */
+
+void refcount_test_3 ()
+{
+  refcount_ptr p (new counted);
+  refcount_ptr q (new counted);
+}
diff --git a/gcc/timevar.def b/gcc/timevar.def
index 51ec035..72d7e9c 100644
--- a/gcc/timevar.def
+++ b/gcc/timevar.def
@@ -141,6 +141,7 @@ DEFTIMEVAR (TV_CONSTRAINT_SAT        , "constraint satisfaction")
 DEFTIMEVAR (TV_CONSTRAINT_SUB        , "constraint subsumption")
 DEFTIMEVAR (TV_FLATTEN_INLINING      , "flatten inlining")
 DEFTIMEVAR (TV_EARLY_INLINING        , "early inlining heuristics")
+DEFTIMEVAR (TV_HEAP_OPTIMIZATIONS    , "heap optimizations")
 DEFTIMEVAR (TV_INLINE_PARAMETERS     , "inline parameters")
 DEFTIMEVAR (TV_INTEGRATION           , "integration")
 DEFTIMEVAR (TV_TREE_GIMPLIFY	     , "tree gimplify")
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 85bfba7..e4e8d53 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -618,6 +618,7 @@ extern rtl_opt_pass *make_pass_final (gcc::context *ctxt);
 extern rtl_opt_pass *make_pass_rtl_seqabstr (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_release_ssa_names (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_early_inline (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_heap_optimizations (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_inline_parameters (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_update_address_taken (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_convert_switch (gcc::context *ctxt);
diff --git a/pr38318-3.f90 b/pr38318-3.f90
new file mode 100644
index 0000000..89df614
--- /dev/null
+++ b/pr38318-3.f90
@@ -0,0 +1,17 @@
+! Looks like it's only at 110t.copyprop1 that we have:
+!   _1 = __builtin_malloc (12);
+!   [...]
+!   __builtin_free (_1);
+! Before this, it's non-trivial to link up the alloc/dealloc calls
+! Moving "heap" to after pass_copy_prop, it detects the alloc_pair
+
+SUBROUTINE S1(N,A)
+ REAL :: A(3)
+ REAL, DIMENSION(:), ALLOCATABLE :: B
+ DO I=1,N
+   ALLOCATE(B(3))
+   B=-A
+   CALL S2(B)
+   DEALLOCATE(B)
+ ENDDO
+END SUBROUTINE
diff --git a/pr38318-main.f90 b/pr38318-main.f90
new file mode 100644
index 0000000..a5b7916e
--- /dev/null
+++ b/pr38318-main.f90
@@ -0,0 +1,30 @@
+SUBROUTINE S2(A)
+ REAL :: A(*),D
+ COMMON /F/D
+ D=D+A(1)+A(2)+A(3)
+END SUBROUTINE
+
+INTEGER, PARAMETER :: N=100000
+REAL :: A(3),T1,T2,T3,T4,D
+COMMON /F/D
+D=0.0
+A=0.0
+CALL CPU_TIME(T1)
+DO I=1,10000
+  CALL S1(N,A)
+ENDDO
+CALL CPU_TIME(T2)
+DO I=1,10000
+  CALL S1_opt1(N,A)
+ENDDO
+CALL CPU_TIME(T3)
+DO I=1,10000
+  CALL S1_opt2(N,A)
+ENDDO
+CALL CPU_TIME(T4)
+
+write(6,*) "Default [s]:",T2-T1
+write(6,*) "OPT1 [s]:",T3-T2
+write(6,*) "OPT2 [s]:",T4-T3
+write(6,*) D
+END
diff --git a/pr38318-test.f90 b/pr38318-test.f90
new file mode 100644
index 0000000..1c836f7
--- /dev/null
+++ b/pr38318-test.f90
@@ -0,0 +1,26 @@
+SUBROUTINE S1(N,A)
+ REAL :: A(3)
+ DO I=1,N
+   CALL S2(-A)
+ ENDDO
+END SUBROUTINE
+
+SUBROUTINE S1_opt1(N,A)
+ REAL :: A(3)
+ REAL, ALLOCATABLE :: B(:)
+ ALLOCATE(B(SIZE(A,1)))
+ DO I=1,N
+   B=-A
+   CALL S2(B)
+ ENDDO
+END SUBROUTINE
+
+
+SUBROUTINE S1_opt2(N,A)
+ REAL :: A(3),B(3)
+ DO I=1,N
+   B=-A
+   CALL S2(B)
+ ENDDO
+END SUBROUTINE
+
diff --git a/pr42958-c17.f90 b/pr42958-c17.f90
new file mode 100644
index 0000000..b1d1606
--- /dev/null
+++ b/pr42958-c17.f90
@@ -0,0 +1,7 @@
+! dse eliminates the usage of the malloc/free buffer
+! cddce then eliminates the whole function body, including the malloc/free
+
+subroutine sub(n)
+  integer :: a(n)
+  a(1) = 0
+end
diff --git a/pr42958-c18.f90 b/pr42958-c18.f90
new file mode 100644
index 0000000..9ddcf6c
--- /dev/null
+++ b/pr42958-c18.f90
@@ -0,0 +1,5 @@
+subroutine test0(esss,Ix)
+  integer(kind=kind(1)), dimension(:), pointer :: esss
+  integer(kind=kind(1)), dimension(:), pointer :: Ix
+  esss = Ix + Ix
+end subroutine
diff --git a/pr42958-c23.c b/pr42958-c23.c
new file mode 100644
index 0000000..4940eae
--- /dev/null
+++ b/pr42958-c23.c
@@ -0,0 +1,75 @@
+__attribute__((noinline, noclone))
+void f1 (int *x, int *y, int *z, int n)
+{
+  int i, j;
+  for (i = 0; i < 500000; i++)
+    {
+      int *temp = __builtin_malloc (n * sizeof (int));
+      for (j = 0; j < n; j++)
+        temp[j] = y[j] * z[j];
+      for (j = 0; j < n; j++)
+        x[j] = temp[j];
+      __builtin_free (temp);
+    }
+}
+
+__attribute__((noinline, noclone))
+void f2 (int *x, int *y, int *z, int n)
+{
+  int i, j;
+  int *temp = __builtin_malloc (n * sizeof (int));
+  for (i = 0; i < 500000; i++)
+    {
+      for (j = 0; j < n; j++)
+        temp[j] = y[j] * z[j];
+      for (j = 0; j < n; j++)
+        x[j] = temp[j];
+    }
+  __builtin_free (temp);
+}
+
+__attribute__((noinline, noclone))
+void f3 (int *x, int *y, int *z, int n)
+{
+  int i, j;
+  for (i = 0; i < 500000; i++)
+    {
+      if ((x + n <= y || x >= y + n)
+          && (x + n <= z || x >= y + n))
+        {
+          for (j = 0; j < n; j++)
+            x[j] = y[j] * z[j];
+        }
+      else
+        {
+          int *temp = __builtin_malloc (n * sizeof (int));
+          for (j = 0; j < n; j++)
+	    temp[j] = y[j] * z[j];
+	  for (j = 0; j < n; j++)
+	    x[j] = temp[j];
+	  __builtin_free (temp);
+        }
+    }
+}
+
+int a[1000], b[1000], c[1000];
+
+int
+main (int argc, char **argv)
+{
+  int i;
+  for (i = 0; i < 1000; i++)
+    {
+      a[i] = 6;
+      b[i] = 9;
+      c[i] = 12;
+    }
+  switch (*argv[1])
+    {
+    case '1': f1 (a, b, c, 1000); break;
+    case '2': f2 (a, b, c, 1000); break;
+    case '3': f3 (a, b, c, 1000); break;
+    default: break;
+    }
+  return 0;
+}
diff --git a/pr42958-tonto.f90 b/pr42958-tonto.f90
new file mode 100644
index 0000000..d03fe30
--- /dev/null
+++ b/pr42958-tonto.f90
@@ -0,0 +1,64 @@
+! There's a malloc/free pair at:
+!    Iyz(:,ii) = Izz * Iy(:,y)
+! within a loop.
+! Handling the size may be non-trivial though...
+
+module test
+
+   type shell1quartet_type
+
+   integer(kind=kind(1)) :: ab_l_sum
+   integer(kind=kind(1)), dimension(:), pointer :: ab_form_3dints_x_indices => NULL()
+   integer(kind=kind(1)), dimension(:), pointer :: ab_form_3dints_yz_rms_indices => NULL()
+
+    end type
+
+contains
+
+! uncomment me for seeing LTO aliasing view
+!subroutine create_(self,dim1,dim2)
+!    real(kind=kind(1.0d0)), dimension(:,:) :: self
+!    pointer :: self
+!    integer(kind=kind(1)), intent(in) :: dim1,dim2
+!    nullify(self)
+!    allocate(self(dim1,dim2))
+!end subroutine
+
+subroutine make_esss(self,esss)
+  type(shell1quartet_type) :: self
+  intent(in) :: self
+  real(kind=kind(1.0d0)), dimension(:), intent(out) :: esss
+  real(kind=kind(1.0d0)), dimension(:), pointer :: Izz
+  real(kind=kind(1.0d0)), dimension(:,:), pointer :: Ix,Iy,Iz,Iyz
+  integer(kind=kind(1)), dimension(:), pointer  :: e_x,ii_ivec
+  integer(kind=kind(1)) :: dim, dim1, nroots, ii,z,y
+
+    dim = self%ab_l_sum+1
+    dim1 = self%ab_l_sum+2
+    nroots = (dim1) / 2
+    call create_(Ix,nroots,dim)
+    call create_(Iy,nroots,dim)
+    call create_(Iz,nroots,dim)
+    call create_(Iyz,nroots,dim*dim1/2)
+
+    e_x => self%ab_form_3dints_x_indices
+    ii_ivec => self%ab_form_3dints_yz_rms_indices
+
+    call foo(Ix)
+    call foo(Iy)
+    call foo(Iz)
+
+    esss = ZERO
+    ii = 0
+    do z=1,dim
+      Izz => Iz(:,z)
+      do y=1,dim1-z
+        ii = ii + 1
+        Iyz(:,ii) = Izz * Iy(:,y)
+      end do
+    end do
+    esss = esss + sum(Ix(:,e_x) * Iyz(:,ii_ivec),1)
+
+end subroutine
+
+end
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..844b785
--- /dev/null
+++ b/test.c
@@ -0,0 +1,51 @@
+#include <stdlib.h>
+
+struct node
+{
+  int value;
+  struct node *next;
+};
+
+static struct node *
+prepend_node (struct node *ptr, int value)
+{
+  struct node *node = malloc (sizeof (struct node));
+  node->value = value;
+  node->next = ptr;
+  return node;
+}
+
+static void
+free_node (struct node *node)
+{
+  free (node);
+}
+
+static int
+visit_nodes (struct node *ptr)
+{
+  int sum = 0;
+  for (; ptr; ptr = ptr->next)
+    sum += ptr->value;
+  return sum;
+}
+
+int
+main (int argc, const char *argv[])
+{
+  struct node *a, *b, *c;
+  int result;
+
+  a = prepend_node (NULL, 3);
+  b = prepend_node (a, 4);
+  c = prepend_node (b, 5);
+
+  result = visit_nodes (c);
+
+  free_node (c);
+  free_node (b);
+  free_node (a);
+  /* What if order is wrong?  */
+
+  return result;
+}
-- 
1.8.5.3

