/* Data structure for the modref pass.
   Copyright (C) 2020-2023 Free Software Foundation, Inc.
   Contributed by David Cepelik and Jan Hubicka

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/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "tree.h"
#include "ipa-modref-tree.h"
#include "selftest.h"
#include "tree-ssa-alias.h"
#include "gimple.h"
#include "cgraph.h"
#include "tree-streamer.h"

/* Return true if both accesses are the same.  */
bool
modref_access_node::operator == (modref_access_node &a) const
{
  if (parm_index != a.parm_index)
    return false;
  if (parm_index != MODREF_UNKNOWN_PARM
      && parm_index != MODREF_GLOBAL_MEMORY_PARM)
    {
      if (parm_offset_known != a.parm_offset_known)
	return false;
      if (parm_offset_known
	  && !known_eq (parm_offset, a.parm_offset))
	return false;
    }
  if (range_info_useful_p () != a.range_info_useful_p ())
    return false;
  if (range_info_useful_p ()
      && (!known_eq (a.offset, offset)
	  || !known_eq (a.size, size)
	  || !known_eq (a.max_size, max_size)))
    return false;
  return true;
}

/* Return true A is a subaccess.  */
bool
modref_access_node::contains (const modref_access_node &a) const
{
  poly_int64 aoffset_adj = 0;
  if (parm_index != MODREF_UNKNOWN_PARM)
    {
      if (parm_index != a.parm_index)
	return false;
      if (parm_offset_known)
	{
	   if (!a.parm_offset_known)
	     return false;
	   /* Accesses are never below parm_offset, so look
	      for smaller offset.
	      If access ranges are known still allow merging
	      when bit offsets comparison passes.  */
	   if (!known_le (parm_offset, a.parm_offset)
	       && !range_info_useful_p ())
	     return false;
	   /* We allow negative aoffset_adj here in case
	      there is an useful range.  This is because adding
	      a.offset may result in non-negative offset again.
	      Ubsan fails on val << LOG_BITS_PER_UNIT where val
	      is negative.  */
	   aoffset_adj = (a.parm_offset - parm_offset)
			 * BITS_PER_UNIT;
	}
    }
  if (range_info_useful_p ())
    {
      if (!a.range_info_useful_p ())
	return false;
      /* Sizes of stores are used to check that object is big enough
	 to fit the store, so smaller or unknown store is more general
	 than large store.  */
      if (known_size_p (size)
	  && (!known_size_p (a.size)
	      || !known_le (size, a.size)))
	return false;
      if (known_size_p (max_size))
	return known_subrange_p (a.offset + aoffset_adj,
				 a.max_size, offset, max_size);
      else
	return known_le (offset, a.offset + aoffset_adj);
    }
  return true;
}

/* Update access range to new parameters.
   If RECORD_ADJUSTMENTS is true, record number of changes in the access
   and if threshold is exceeded start dropping precision
   so only constantly many updates are possible.  This makes dataflow
   to converge.  */
void
modref_access_node::update (poly_int64 parm_offset1,
			    poly_int64 offset1, poly_int64 size1,
			    poly_int64 max_size1, bool record_adjustments)
{
  if (known_eq (parm_offset, parm_offset1)
      && known_eq (offset, offset1)
      && known_eq (size, size1)
      && known_eq (max_size, max_size1))
    return;
  if (!record_adjustments
      || (++adjustments) < param_modref_max_adjustments)
    {
      parm_offset = parm_offset1;
      offset = offset1;
      size = size1;
      max_size = max_size1;
    }
  else
    {
      if (dump_file)
	fprintf (dump_file, "--param modref-max-adjustments limit reached:");
      if (!known_eq (parm_offset, parm_offset1))
	{
	  if (dump_file)
	    fprintf (dump_file, " parm_offset cleared");
	  parm_offset_known = false;
	}
      if (!known_eq (size, size1))
	{
	  size = -1;
	  if (dump_file)
	    fprintf (dump_file, " size cleared");
	}
      if (!known_eq (max_size, max_size1))
	{
	  max_size = -1;
	  if (dump_file)
	    fprintf (dump_file, " max_size cleared");
	}
      if (!known_eq (offset, offset1))
	{
	  offset = 0;
	  if (dump_file)
	    fprintf (dump_file, " offset cleared");
	}
      if (dump_file)
	fprintf (dump_file, "\n");
    }
}

/* Merge in access A if it is possible to do without losing
   precision.  Return true if successful.
   If RECORD_ADJUSTMENTs is true, remember how many interval
   was prolonged and punt when there are too many.  */
bool
modref_access_node::merge (const modref_access_node &a,
			   bool record_adjustments)
{
  poly_int64 offset1 = 0;
  poly_int64 aoffset1 = 0;
  poly_int64 new_parm_offset = 0;

  /* We assume that containment was tested earlier.  */
  gcc_checking_assert (!contains (a) && !a.contains (*this));
  if (parm_index != MODREF_UNKNOWN_PARM)
    {
      if (parm_index != a.parm_index)
	return false;
      if (parm_offset_known)
	{
	  if (!a.parm_offset_known)
	    return false;
	  if (!combined_offsets (a, &new_parm_offset, &offset1, &aoffset1))
	    return false;
	}
    }
  /* See if we can merge ranges.  */
  if (range_info_useful_p ())
    {
      /* In this case we have containment that should be
	 handled earlier.  */
      gcc_checking_assert (a.range_info_useful_p ());

      /* If a.size is less specified than size, merge only
	 if intervals are otherwise equivalent.  */
      if (known_size_p (size)
	  && (!known_size_p (a.size) || known_lt (a.size, size)))
	{
	  if (((known_size_p (max_size) || known_size_p (a.max_size))
	       && !known_eq (max_size, a.max_size))
	       || !known_eq (offset1, aoffset1))
	    return false;
	  update (new_parm_offset, offset1, a.size, max_size,
		  record_adjustments);
	  return true;
	}
      /* If sizes are same, we can extend the interval.  */
      if ((known_size_p (size) || known_size_p (a.size))
	  && !known_eq (size, a.size))
	return false;
      if (known_le (offset1, aoffset1))
	{
	  if (!known_size_p (max_size)
	      || known_ge (offset1 + max_size, aoffset1))
	    {
	      update2 (new_parm_offset, offset1, size, max_size,
		       aoffset1, a.size, a.max_size,
		       record_adjustments);
	      return true;
	    }
	}
      else if (known_le (aoffset1, offset1))
	{
	  if (!known_size_p (a.max_size)
	      || known_ge (aoffset1 + a.max_size, offset1))
	    {
	      update2 (new_parm_offset, offset1, size, max_size,
		       aoffset1, a.size, a.max_size,
		       record_adjustments);
	      return true;
	    }
	}
      return false;
    }
  update (new_parm_offset, offset1,
	  size, max_size, record_adjustments);
  return true;
}

/* Return true if A1 and B1 can be merged with lower information
   less than A2 and B2.
   Assume that no containment or lossless merging is possible.  */
bool
modref_access_node::closer_pair_p (const modref_access_node &a1,
				   const modref_access_node &b1,
				   const modref_access_node &a2,
				   const modref_access_node &b2)
{
  /* Merging different parm indexes comes to complete loss
     of range info.  */
  if (a1.parm_index != b1.parm_index)
    return false;
  if (a2.parm_index != b2.parm_index)
    return true;
  /* If parm is known and parm indexes are the same we should
     already have containment.  */
  gcc_checking_assert (a1.parm_offset_known && b1.parm_offset_known);
  gcc_checking_assert (a2.parm_offset_known && b2.parm_offset_known);

  /* First normalize offsets for parm offsets.  */
  poly_int64 new_parm_offset, offseta1, offsetb1, offseta2, offsetb2;
  if (!a1.combined_offsets (b1, &new_parm_offset, &offseta1, &offsetb1)
      || !a2.combined_offsets (b2, &new_parm_offset, &offseta2, &offsetb2))
    gcc_unreachable ();


  /* Now compute distance of the intervals.  */
  poly_offset_int dist1, dist2;
  if (known_le (offseta1, offsetb1))
    {
      if (!known_size_p (a1.max_size))
	dist1 = 0;
      else
	dist1 = (poly_offset_int)offsetb1
		- (poly_offset_int)offseta1
		- (poly_offset_int)a1.max_size;
    }
  else
    {
      if (!known_size_p (b1.max_size))
	dist1 = 0;
      else
	dist1 = (poly_offset_int)offseta1
		 - (poly_offset_int)offsetb1
		 - (poly_offset_int)b1.max_size;
    }
  if (known_le (offseta2, offsetb2))
    {
      if (!known_size_p (a2.max_size))
	dist2 = 0;
      else
	dist2 = (poly_offset_int)offsetb2
		- (poly_offset_int)offseta2
		- (poly_offset_int)a2.max_size;
    }
  else
    {
      if (!known_size_p (b2.max_size))
	dist2 = 0;
      else
	dist2 = offseta2
		- (poly_offset_int)offsetb2
		- (poly_offset_int)b2.max_size;
    }
  /* It may happen that intervals overlap in case size
     is different.  Prefer the overlap to non-overlap.  */
  if (known_lt (dist1, 0) && known_ge (dist2, 0))
    return true;
  if (known_lt (dist2, 0) && known_ge (dist1, 0))
    return false;
  if (known_lt (dist1, 0))
    /* If both overlaps minimize overlap.  */
    return known_le (dist2, dist1);
  else
    /* If both are disjoint look for smaller distance.  */
    return known_le (dist1, dist2);
}

/* Merge in access A while losing precision.  */
void
modref_access_node::forced_merge (const modref_access_node &a,
				  bool record_adjustments)
{
  if (parm_index != a.parm_index)
    {
      gcc_checking_assert (parm_index != MODREF_UNKNOWN_PARM);
      parm_index = MODREF_UNKNOWN_PARM;
      return;
    }

  /* We assume that containment and lossless merging
     was tested earlier.  */
  gcc_checking_assert (!contains (a) && !a.contains (*this)
		       && !merge (a, record_adjustments));
  gcc_checking_assert (parm_offset_known && a.parm_offset_known);

  poly_int64 new_parm_offset, offset1, aoffset1;
  if (!combined_offsets (a, &new_parm_offset, &offset1, &aoffset1))
    {
      parm_offset_known = false;
      return;
    }
  gcc_checking_assert (range_info_useful_p ()
		       && a.range_info_useful_p ());
  if (record_adjustments)
    adjustments += a.adjustments;
  update2 (new_parm_offset,
	   offset1, size, max_size,
	   aoffset1, a.size, a.max_size,
	   record_adjustments);
}

/* Merge two ranges both starting at parm_offset1 and update THIS
   with result.  */
void
modref_access_node::update2 (poly_int64 parm_offset1,
			     poly_int64 offset1, poly_int64 size1,
			     poly_int64 max_size1,
			     poly_int64 offset2, poly_int64 size2,
			     poly_int64 max_size2,
			     bool record_adjustments)
{
  poly_int64 new_size = size1;

  if (!known_size_p (size2)
      || known_le (size2, size1))
    new_size = size2;
  else
    gcc_checking_assert (known_le (size1, size2));

  if (known_le (offset1, offset2))
    ;
  else if (known_le (offset2, offset1))
    {
      std::swap (offset1, offset2);
      std::swap (max_size1, max_size2);
    }
  else
    gcc_unreachable ();

  poly_int64 new_max_size;

  if (!known_size_p (max_size1))
    new_max_size = max_size1;
  else if (!known_size_p (max_size2))
    new_max_size = max_size2;
  else
    {
      poly_offset_int s = (poly_offset_int)max_size2
			  + (poly_offset_int)offset2
			  - (poly_offset_int)offset1;
      if (s.to_shwi (&new_max_size))
	{
	  if (known_le (new_max_size, max_size1))
	    new_max_size = max_size1;
	}
      else
	new_max_size = -1;
    }

  update (parm_offset1, offset1,
	  new_size, new_max_size, record_adjustments);
}

/* Given access nodes THIS and A, return true if they
   can be done with common parm_offsets.  In this case
   return parm offset in new_parm_offset, new_offset
   which is start of range in THIS and new_aoffset that
   is start of range in A.  */
bool
modref_access_node::combined_offsets (const modref_access_node &a,
				      poly_int64 *new_parm_offset,
				      poly_int64 *new_offset,
				      poly_int64 *new_aoffset) const
{
  gcc_checking_assert (parm_offset_known && a.parm_offset_known);
  if (known_le (a.parm_offset, parm_offset))
    {
      *new_offset = offset
		    + ((parm_offset - a.parm_offset)
		       << LOG2_BITS_PER_UNIT);
      *new_aoffset = a.offset;
      *new_parm_offset = a.parm_offset;
      return true;
    }
  else if (known_le (parm_offset, a.parm_offset))
    {
      *new_aoffset = a.offset
		      + ((a.parm_offset - parm_offset)
			 << LOG2_BITS_PER_UNIT);
      *new_offset = offset;
      *new_parm_offset = parm_offset;
      return true;
    }
  else
    return false;
}

/* Try to optimize the access ACCESSES list after entry INDEX was modified.  */
void
modref_access_node::try_merge_with (vec <modref_access_node, va_gc> *&accesses,
				    size_t index)
{
  size_t i;

  for (i = 0; i < accesses->length ();)
    if (i != index)
      {
	bool found = false, restart = false;
	modref_access_node *a = &(*accesses)[i];
	modref_access_node *n = &(*accesses)[index];

	if (n->contains (*a))
	  found = true;
	if (!found && n->merge (*a, false))
	  found = restart = true;
	gcc_checking_assert (found || !a->merge (*n, false));
	if (found)
	  {
	    accesses->unordered_remove (i);
	    if (index == accesses->length ())
	      {
		index = i;
		i++;
	      }
	    if (restart)
	      i = 0;
	  }
	else
	  i++;
      }
    else
      i++;
}

/* Stream out to OB.  */

void
modref_access_node::stream_out (struct output_block *ob) const
{
  streamer_write_hwi (ob, parm_index);
  if (parm_index != MODREF_UNKNOWN_PARM)
    {
      streamer_write_uhwi (ob, parm_offset_known);
      if (parm_offset_known)
	{
	  streamer_write_poly_int64 (ob, parm_offset);
	  streamer_write_poly_int64 (ob, offset);
	  streamer_write_poly_int64 (ob, size);
	  streamer_write_poly_int64 (ob, max_size);
	}
    }
}

modref_access_node
modref_access_node::stream_in (struct lto_input_block *ib)
{
  int parm_index = streamer_read_hwi (ib);
  bool parm_offset_known = false;
  poly_int64 parm_offset = 0;
  poly_int64 offset = 0;
  poly_int64 size = -1;
  poly_int64 max_size = -1;

  if (parm_index != MODREF_UNKNOWN_PARM)
    {
      parm_offset_known = streamer_read_uhwi (ib);
      if (parm_offset_known)
	{
	  parm_offset = streamer_read_poly_int64 (ib);
	  offset = streamer_read_poly_int64 (ib);
	  size = streamer_read_poly_int64 (ib);
	  max_size = streamer_read_poly_int64 (ib);
	}
    }
  return {offset, size, max_size, parm_offset, parm_index,
	  parm_offset_known, false};
}

/* Insert access with OFFSET and SIZE.
   Collapse tree if it has more than MAX_ACCESSES entries.
   If RECORD_ADJUSTMENTs is true avoid too many interval extensions.
   Return true if record was changed.

   Return 0 if nothing changed, 1 if insert was successful and -1
   if entries should be collapsed.  */
int
modref_access_node::insert (vec <modref_access_node, va_gc> *&accesses,
			    modref_access_node a, size_t max_accesses,
			    bool record_adjustments)
{
  size_t i, j;
  modref_access_node *a2;

  /* Verify that list does not contain redundant accesses.  */
  if (flag_checking)
    {
      size_t i, i2;
      modref_access_node *a, *a2;

      FOR_EACH_VEC_SAFE_ELT (accesses, i, a)
	{
	  FOR_EACH_VEC_SAFE_ELT (accesses, i2, a2)
	    if (i != i2)
	      gcc_assert (!a->contains (*a2));
	}
    }

  FOR_EACH_VEC_SAFE_ELT (accesses, i, a2)
    {
      if (a2->contains (a))
	return 0;
      if (a.contains (*a2))
	{
	  a.adjustments = 0;
	  a2->parm_index = a.parm_index;
	  a2->parm_offset_known = a.parm_offset_known;
	  a2->update (a.parm_offset, a.offset, a.size, a.max_size,
		      record_adjustments);
	  modref_access_node::try_merge_with (accesses, i);
	  return 1;
	}
      if (a2->merge (a, record_adjustments))
	{
	  modref_access_node::try_merge_with (accesses, i);
	  return 1;
	}
      gcc_checking_assert (!(a == *a2));
    }

  /* If this base->ref pair has too many accesses stored, we will clear
     all accesses and bail out.  */
  if (accesses && accesses->length () >= max_accesses)
    {
      if (max_accesses < 2)
	return -1;
      /* Find least harmful merge and perform it.  */
      int best1 = -1, best2 = -1;
      FOR_EACH_VEC_SAFE_ELT (accesses, i, a2)
	{
	  for (j = i + 1; j < accesses->length (); j++)
	    if (best1 < 0
		|| modref_access_node::closer_pair_p
		     (*a2, (*accesses)[j],
		      (*accesses)[best1],
		      best2 < 0 ? a : (*accesses)[best2]))
	      {
		best1 = i;
		best2 = j;
	      }
	  if (modref_access_node::closer_pair_p
		     (*a2, a,
		      (*accesses)[best1],
		      best2 < 0 ? a : (*accesses)[best2]))
	    {
	      best1 = i;
	      best2 = -1;
	    }
	}
      (*accesses)[best1].forced_merge (best2 < 0 ? a : (*accesses)[best2],
				       record_adjustments);
      /* Check that merging indeed merged ranges.  */
      gcc_checking_assert ((*accesses)[best1].contains
			       (best2 < 0 ? a : (*accesses)[best2]));
      if (!(*accesses)[best1].useful_p ())
	return -1;
      if (dump_file && best2 >= 0)
	fprintf (dump_file,
		 "--param modref-max-accesses limit reached;"
		 " merging %i and %i\n", best1, best2);
      else if (dump_file)
	fprintf (dump_file,
		 "--param modref-max-accesses limit reached;"
		 " merging with %i\n", best1);
      modref_access_node::try_merge_with (accesses, best1);
      if (best2 >= 0)
	insert (accesses, a, max_accesses, record_adjustments);
      return 1;
    }
  a.adjustments = 0;
  vec_safe_push (accesses, a);
  return 1;
}

/* Return true if range info is useful.  */
bool
modref_access_node::range_info_useful_p () const
{
  return parm_index != MODREF_UNKNOWN_PARM
	 && parm_index != MODREF_GLOBAL_MEMORY_PARM
	 && parm_offset_known
	 && (known_size_p (size)
	     || known_size_p (max_size)
	     || known_ge (offset, 0));
}

/* Dump range to debug OUT.  */
void
modref_access_node::dump (FILE *out)
{
  if (parm_index != MODREF_UNKNOWN_PARM)
    {
      if (parm_index == MODREF_GLOBAL_MEMORY_PARM)
	fprintf (out, " Base in global memory");
      else if (parm_index >= 0)
	fprintf (out, " Parm %i", parm_index);
      else if (parm_index == MODREF_STATIC_CHAIN_PARM)
	fprintf (out, " Static chain");
      else
	gcc_unreachable ();
      if (parm_offset_known)
	{
	  fprintf (out, " param offset:");
	  print_dec ((poly_int64_pod)parm_offset, out, SIGNED);
	}
    }
  if (range_info_useful_p ())
    {
      fprintf (out, " offset:");
      print_dec ((poly_int64_pod)offset, out, SIGNED);
      fprintf (out, " size:");
      print_dec ((poly_int64_pod)size, out, SIGNED);
      fprintf (out, " max_size:");
      print_dec ((poly_int64_pod)max_size, out, SIGNED);
      if (adjustments)
	fprintf (out, " adjusted %i times", adjustments);
    }
  fprintf (out, "\n");
}

/* Return tree corresponding to parameter of the range in STMT.  */
tree
modref_access_node::get_call_arg (const gcall *stmt) const
{
  if (parm_index == MODREF_UNKNOWN_PARM
      || parm_index == MODREF_GLOBAL_MEMORY_PARM)
    return NULL;
  if (parm_index == MODREF_STATIC_CHAIN_PARM)
    return gimple_call_chain (stmt);
  /* MODREF_RETSLOT_PARM should not happen in access trees since the store
     is seen explicitly in the caller.  */
  gcc_checking_assert (parm_index >= 0);
  if (parm_index >= (int)gimple_call_num_args (stmt))
    return NULL;
  return gimple_call_arg (stmt, parm_index);
}

/* Return tree corresponding to parameter of the range in STMT.  */
bool
modref_access_node::get_ao_ref (const gcall *stmt, ao_ref *ref) const
{
  tree arg;

  if (!parm_offset_known
      || !(arg = get_call_arg (stmt))
      || !POINTER_TYPE_P (TREE_TYPE (arg)))
    return false;
  poly_offset_int off = (poly_offset_int)offset
	+ ((poly_offset_int)parm_offset << LOG2_BITS_PER_UNIT);
  poly_int64 off2;
  if (!off.to_shwi (&off2))
    return false;
  ao_ref_init_from_ptr_and_range (ref, arg, true, off2, size, max_size);
  return true;
}

/* Return true A is a subkill.  */
bool
modref_access_node::contains_for_kills (const modref_access_node &a) const
{
  poly_int64 aoffset_adj = 0;

  gcc_checking_assert (parm_index != MODREF_UNKNOWN_PARM
		       && a.parm_index != MODREF_UNKNOWN_PARM);
  if (parm_index != a.parm_index)
    return false;
  gcc_checking_assert (parm_offset_known && a.parm_offset_known);
  aoffset_adj = (a.parm_offset - parm_offset)
		* BITS_PER_UNIT;
  gcc_checking_assert (range_info_useful_p () && a.range_info_useful_p ());
  return known_subrange_p (a.offset + aoffset_adj,
			   a.max_size, offset, max_size);
}

/* Merge two ranges both starting at parm_offset1 and update THIS
   with result.  */
bool
modref_access_node::update_for_kills (poly_int64 parm_offset1,
				      poly_int64 offset1,
				      poly_int64 max_size1,
				      poly_int64 offset2,
				      poly_int64 max_size2,
				      bool record_adjustments)
{
  if (known_le (offset1, offset2))
    ;
  else if (known_le (offset2, offset1))
    {
      std::swap (offset1, offset2);
      std::swap (max_size1, max_size2);
    }
  else
    gcc_unreachable ();

  poly_int64 new_max_size = max_size2 + offset2 - offset1;
  if (known_le (new_max_size, max_size1))
    new_max_size = max_size1;
  if (known_eq (parm_offset, parm_offset1)
      && known_eq (offset, offset1)
      && known_eq (size, new_max_size)
      && known_eq (max_size, new_max_size))
    return false;

  if (!record_adjustments
      || (++adjustments) < param_modref_max_adjustments)
    {
      parm_offset = parm_offset1;
      offset = offset1;
      max_size = new_max_size;
      size = new_max_size;
      gcc_checking_assert (useful_for_kill_p ());
      return true;
    }
  return false;
}

/* Merge in access A if it is possible to do without losing
   precision.  Return true if successful.
   Unlike merge assume that both accesses are always executed
   and merge size the same was as max_size.  */
bool
modref_access_node::merge_for_kills (const modref_access_node &a,
				     bool record_adjustments)
{
  poly_int64 offset1 = 0;
  poly_int64 aoffset1 = 0;
  poly_int64 new_parm_offset = 0;

  /* We assume that containment was tested earlier.  */
  gcc_checking_assert (!contains_for_kills (a) && !a.contains_for_kills (*this)
		       && useful_for_kill_p () && a.useful_for_kill_p ());

  if (parm_index != a.parm_index
      || !combined_offsets (a, &new_parm_offset, &offset1, &aoffset1))
    return false;

  if (known_le (offset1, aoffset1))
   {
     if (!known_size_p (max_size)
	 || known_ge (offset1 + max_size, aoffset1))
       return update_for_kills (new_parm_offset, offset1, max_size,
				aoffset1, a.max_size, record_adjustments);
   }
  else if (known_le (aoffset1, offset1))
   {
     if (!known_size_p (a.max_size)
	 || known_ge (aoffset1 + a.max_size, offset1))
       return update_for_kills (new_parm_offset, offset1, max_size,
				aoffset1, a.max_size, record_adjustments);
   }
  return false;
}

/* Insert new kill A into KILLS.  If RECORD_ADJUSTMENTS is true limit number
   of changes to each entry.  Return true if something changed.  */

bool
modref_access_node::insert_kill (vec<modref_access_node> &kills,
				 modref_access_node &a, bool record_adjustments)
{
  size_t index;
  modref_access_node *a2;
  bool merge = false;

  gcc_checking_assert (a.useful_for_kill_p ());

  /* See if we have corresponding entry already or we can merge with
     neighboring entry.  */
  FOR_EACH_VEC_ELT (kills, index, a2)
    {
      if (a2->contains_for_kills (a))
	return false;
      if (a.contains_for_kills (*a2))
	{
	  a.adjustments = 0;
	  *a2 = a;
	  merge = true;
	  break;
	}
      if (a2->merge_for_kills (a, record_adjustments))
	{
	  merge = true;
	  break;
	}
    }
  /* If entry was not found, insert it.  */
  if (!merge)
    {
      if ((int)kills.length () >= param_modref_max_accesses)
	{
	  if (dump_file)
	    fprintf (dump_file, "--param modref-max-accesses limit reached:");
	  return false;
	}
      a.adjustments = 0;
      kills.safe_push (a);
      return true;
    }
  /* Extending range in an entry may make it possible to merge it with
     other entries.  */
  size_t i;

  for (i = 0; i < kills.length ();)
    if (i != index)
      {
	bool found = false, restart = false;
	modref_access_node *a = &kills[i];
	modref_access_node *n = &kills[index];

	if (n->contains_for_kills (*a))
	  found = true;
	if (!found && n->merge_for_kills (*a, false))
	  found = restart = true;
	gcc_checking_assert (found || !a->merge_for_kills (*n, false));
	if (found)
	  {
	    kills.unordered_remove (i);
	    if (index == kills.length ())
	      {
		index = i;
		i++;
	      }
	    if (restart)
	      i = 0;
	  }
	else
	  i++;
      }
    else
      i++;
  return true;
}


#if CHECKING_P

namespace selftest {

static void
test_insert_search_collapse ()
{
  modref_base_node<alias_set_type> *base_node;
  modref_ref_node<alias_set_type> *ref_node;
  modref_access_node a = unspecified_modref_access_node;

  modref_tree<alias_set_type> *t = new modref_tree<alias_set_type>();
  ASSERT_FALSE (t->every_base);

  /* Insert into an empty tree.  */
  t->insert (1, 2, 2, 1, 2, a, false);
  ASSERT_NE (t->bases, NULL);
  ASSERT_EQ (t->bases->length (), 1);
  ASSERT_FALSE (t->every_base);
  ASSERT_EQ (t->search (2), NULL);

  base_node = t->search (1);
  ASSERT_NE (base_node, NULL);
  ASSERT_EQ (base_node->base, 1);
  ASSERT_NE (base_node->refs, NULL);
  ASSERT_EQ (base_node->refs->length (), 1);
  ASSERT_EQ (base_node->search (1), NULL);

  ref_node = base_node->search (2);
  ASSERT_NE (ref_node, NULL);
  ASSERT_EQ (ref_node->ref, 2);

  /* Insert when base exists but ref does not.  */
  t->insert (1, 2, 2, 1, 3, a, false);
  ASSERT_NE (t->bases, NULL);
  ASSERT_EQ (t->bases->length (), 1);
  ASSERT_EQ (t->search (1), base_node);
  ASSERT_EQ (t->search (2), NULL);
  ASSERT_NE (base_node->refs, NULL);
  ASSERT_EQ (base_node->refs->length (), 2);

  ref_node = base_node->search (3);
  ASSERT_NE (ref_node, NULL);

  /* Insert when base and ref exist, but access is not dominated by nor
     dominates other accesses.  */
  t->insert (1, 2, 2, 1, 2, a, false);
  ASSERT_EQ (t->bases->length (), 1);
  ASSERT_EQ (t->search (1), base_node);

  ref_node = base_node->search (2);
  ASSERT_NE (ref_node, NULL);

  /* Insert when base and ref exist and access is dominated.  */
  t->insert (1, 2, 2, 1, 2, a, false);
  ASSERT_EQ (t->search (1), base_node);
  ASSERT_EQ (base_node->search (2), ref_node);

  /* Insert ref to trigger ref list collapse for base 1.  */
  t->insert (1, 2, 2, 1, 4, a, false);
  ASSERT_EQ (t->search (1), base_node);
  ASSERT_EQ (base_node->refs, NULL);
  ASSERT_EQ (base_node->search (2), NULL);
  ASSERT_EQ (base_node->search (3), NULL);
  ASSERT_TRUE (base_node->every_ref);

  /* Further inserts to collapsed ref list are ignored.  */
  t->insert (1, 2, 2, 1, 5, a, false);
  ASSERT_EQ (t->search (1), base_node);
  ASSERT_EQ (base_node->refs, NULL);
  ASSERT_EQ (base_node->search (2), NULL);
  ASSERT_EQ (base_node->search (3), NULL);
  ASSERT_TRUE (base_node->every_ref);

  /* Insert base to trigger base list collapse.  */
  t->insert (1, 2, 2, 5, 0, a, false);
  ASSERT_TRUE (t->every_base);
  ASSERT_EQ (t->bases, NULL);
  ASSERT_EQ (t->search (1), NULL);

  /* Further inserts to collapsed base list are ignored.  */
  t->insert (1, 2, 2, 7, 8, a, false);
  ASSERT_TRUE (t->every_base);
  ASSERT_EQ (t->bases, NULL);
  ASSERT_EQ (t->search (1), NULL);

  delete t;
}

static void
test_merge ()
{
  modref_tree<alias_set_type> *t1, *t2;
  modref_base_node<alias_set_type> *base_node;
  modref_access_node a = unspecified_modref_access_node;

  t1 = new modref_tree<alias_set_type>();
  t1->insert (3, 4, 1, 1, 1, a, false);
  t1->insert (3, 4, 1, 1, 2, a, false);
  t1->insert (3, 4, 1, 1, 3, a, false);
  t1->insert (3, 4, 1, 2, 1, a, false);
  t1->insert (3, 4, 1, 3, 1, a, false);

  t2 = new modref_tree<alias_set_type>();
  t2->insert (10, 10, 10, 1, 2, a, false);
  t2->insert (10, 10, 10, 1, 3, a, false);
  t2->insert (10, 10, 10, 1, 4, a, false);
  t2->insert (10, 10, 10, 3, 2, a, false);
  t2->insert (10, 10, 10, 3, 3, a, false);
  t2->insert (10, 10, 10, 3, 4, a, false);
  t2->insert (10, 10, 10, 3, 5, a, false);

  t1->merge (3, 4, 1, t2, NULL, NULL, false);

  ASSERT_FALSE (t1->every_base);
  ASSERT_NE (t1->bases, NULL);
  ASSERT_EQ (t1->bases->length (), 3);

  base_node = t1->search (1);
  ASSERT_NE (base_node->refs, NULL);
  ASSERT_FALSE (base_node->every_ref);
  ASSERT_EQ (base_node->refs->length (), 4);

  base_node = t1->search (2);
  ASSERT_NE (base_node->refs, NULL);
  ASSERT_FALSE (base_node->every_ref);
  ASSERT_EQ (base_node->refs->length (), 1);

  base_node = t1->search (3);
  ASSERT_EQ (base_node->refs, NULL);
  ASSERT_TRUE (base_node->every_ref);

  delete t1;
  delete t2;
}


void
ipa_modref_tree_cc_tests ()
{
  test_insert_search_collapse ();
  test_merge ();
}

} // namespace selftest

#endif

void
gt_ggc_mx (modref_tree < int >*const &tt)
{
  if (tt->bases)
    {
      ggc_test_and_set_mark (tt->bases);
      gt_ggc_mx (tt->bases);
    }
}

void
gt_ggc_mx (modref_tree < tree_node * >*const &tt)
{
  if (tt->bases)
    {
      ggc_test_and_set_mark (tt->bases);
      gt_ggc_mx (tt->bases);
    }
}

void gt_pch_nx (modref_tree<int>* const&) {}
void gt_pch_nx (modref_tree<tree_node*>* const&) {}
void gt_pch_nx (modref_tree<int>* const&, gt_pointer_operator, void *) {}
void gt_pch_nx (modref_tree<tree_node*>* const&, gt_pointer_operator, void *) {}

void gt_ggc_mx (modref_base_node<int>* &b)
{
  ggc_test_and_set_mark (b);
  if (b->refs)
    {
      ggc_test_and_set_mark (b->refs);
      gt_ggc_mx (b->refs);
    }
}

void gt_ggc_mx (modref_base_node<tree_node*>* &b)
{
  ggc_test_and_set_mark (b);
  if (b->refs)
    {
      ggc_test_and_set_mark (b->refs);
      gt_ggc_mx (b->refs);
    }
  if (b->base)
    gt_ggc_mx (b->base);
}

void gt_pch_nx (modref_base_node<int>*) {}
void gt_pch_nx (modref_base_node<tree_node*>*) {}
void gt_pch_nx (modref_base_node<int>*, gt_pointer_operator, void *) {}
void gt_pch_nx (modref_base_node<tree_node*>*, gt_pointer_operator, void *) {}

void gt_ggc_mx (modref_ref_node<int>* &r)
{
  ggc_test_and_set_mark (r);
  if (r->accesses)
    {
      ggc_test_and_set_mark (r->accesses);
      gt_ggc_mx (r->accesses);
    }
}

void gt_ggc_mx (modref_ref_node<tree_node*>* &r)
{
  ggc_test_and_set_mark (r);
  if (r->accesses)
    {
      ggc_test_and_set_mark (r->accesses);
      gt_ggc_mx (r->accesses);
    }
  if (r->ref)
    gt_ggc_mx (r->ref);
}

void gt_pch_nx (modref_ref_node<int>* ) {}
void gt_pch_nx (modref_ref_node<tree_node*>*) {}
void gt_pch_nx (modref_ref_node<int>*, gt_pointer_operator, void *) {}
void gt_pch_nx (modref_ref_node<tree_node*>*, gt_pointer_operator, void *) {}

void gt_ggc_mx (modref_access_node &)
{
}