/* Interprocedural reference lists.
   Copyright (C) 2010-2014 Free Software Foundation, Inc.
   Contributed by 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 "tree.h"
#include "ggc.h"
#include "target.h"
#include "cgraph.h"
#include "ipa-utils.h"

static const char *ipa_ref_use_name[] = {"read","write","addr","alias"};

/* Return ipa reference from REFERING_NODE or REFERING_VARPOOL_NODE
   to REFERED_NODE or REFERED_VARPOOL_NODE. USE_TYPE specify type
   of the use and STMT the statement (if it exists).  */

struct ipa_ref *
ipa_record_reference (symtab_node *referring_node,
		      symtab_node *referred_node,
		      enum ipa_ref_use use_type, gimple stmt)
{
  struct ipa_ref *ref, *ref2;
  struct ipa_ref_list *list, *list2;
  ipa_ref_t *old_references;

  gcc_checking_assert (!stmt || is_a <cgraph_node> (referring_node));
  gcc_checking_assert (use_type != IPA_REF_ALIAS || !stmt);

  list = &referring_node->ref_list;
  old_references = vec_safe_address (list->references);
  vec_safe_grow (list->references, vec_safe_length (list->references) + 1);
  ref = &list->references->last ();

  list2 = &referred_node->ref_list;
  list2->referring.safe_push (ref);
  ref->referred_index = list2->referring.length () - 1;
  ref->referring = referring_node;
  ref->referred = referred_node;
  ref->stmt = stmt;
  ref->lto_stmt_uid = 0;
  ref->use = use_type;
  ref->speculative = 0;

  /* If vector was moved in memory, update pointers.  */
  if (old_references != list->references->address ())
    {
      int i;
      for (i = 0; ipa_ref_list_reference_iterate (list, i, ref2); i++)
	ipa_ref_referred_ref_list (ref2)->referring[ref2->referred_index] = ref2;
    }
  return ref;
}

/* If VAL is a reference to a function or a variable, add a reference from
   REFERRING_NODE to the corresponding symbol table node.  USE_TYPE specify
   type of the use and STMT the statement (if it exists).  Return the new
   reference or NULL if none was created.  */

struct ipa_ref *
ipa_maybe_record_reference (symtab_node *referring_node, tree val,
			    enum ipa_ref_use use_type, gimple stmt)
{
  STRIP_NOPS (val);
  if (TREE_CODE (val) != ADDR_EXPR)
    return NULL;
  val = get_base_var (val);
  if (val && (TREE_CODE (val) == FUNCTION_DECL
	       || TREE_CODE (val) == VAR_DECL))
    {
      symtab_node *referred = symtab_get_node (val);
      gcc_checking_assert (referred);
      return ipa_record_reference (referring_node, referred,
				   use_type, stmt);
    }
  return NULL;
}

/* Remove reference REF.  */

void
ipa_remove_reference (struct ipa_ref *ref)
{
  struct ipa_ref_list *list = ipa_ref_referred_ref_list (ref);
  struct ipa_ref_list *list2 = ipa_ref_referring_ref_list (ref);
  vec<ipa_ref_t, va_gc> *old_references = list2->references;
  struct ipa_ref *last;

  gcc_assert (list->referring[ref->referred_index] == ref);
  last = list->referring.last ();
  if (ref != last)
    {
      list->referring[ref->referred_index] = list->referring.last ();
      list->referring[ref->referred_index]->referred_index 
	  = ref->referred_index;
    }
  list->referring.pop ();

  last = &list2->references->last ();
  if (ref != last)
    {
      *ref = *last;
      ipa_ref_referred_ref_list (ref)->referring[ref->referred_index] = ref;
    }
  list2->references->pop ();
  gcc_assert (list2->references == old_references);
}

/* Remove all references in ref list LIST.  */

void
ipa_remove_all_references (struct ipa_ref_list *list)
{
  while (vec_safe_length (list->references))
    ipa_remove_reference (&list->references->last ());
  vec_free (list->references);
}

/* Remove all references in ref list LIST.  */

void
ipa_remove_all_referring (struct ipa_ref_list *list)
{
  while (list->referring.length ())
    ipa_remove_reference (list->referring.last ());
  list->referring.release ();
}

/* Dump references in LIST to FILE.  */

void
ipa_dump_references (FILE * file, struct ipa_ref_list *list)
{
  struct ipa_ref *ref;
  int i;
  for (i = 0; ipa_ref_list_reference_iterate (list, i, ref); i++)
    {
      fprintf (file, "%s/%i (%s)",
               ref->referred->asm_name (),
               ref->referred->order,
	       ipa_ref_use_name [ref->use]);
      if (ref->speculative)
	fprintf (file, " (speculative)");
    }
  fprintf (file, "\n");
}

/* Dump referring in LIST to FILE.  */

void
ipa_dump_referring (FILE * file, struct ipa_ref_list *list)
{
  struct ipa_ref *ref;
  int i;
  for (i = 0; ipa_ref_list_referring_iterate (list, i, ref); i++)
    {
      fprintf (file, "%s/%i (%s)",
               ref->referring->asm_name (),
               ref->referring->order,
	       ipa_ref_use_name [ref->use]);
      if (ref->speculative)
	fprintf (file, " (speculative)");
    }
  fprintf (file, "\n");
}

/* Clone reference REF to DEST_NODE and set its stmt to STMT.  */

struct ipa_ref *
ipa_clone_ref (struct ipa_ref *ref,
	       symtab_node *dest_node,
	       gimple stmt)
{
  bool speculative = ref->speculative;
  unsigned int stmt_uid = ref->lto_stmt_uid;
  struct ipa_ref *ref2;

  ref2 = ipa_record_reference (dest_node,
			       ref->referred,
			       ref->use, stmt);
  ref2->speculative = speculative;
  ref2->lto_stmt_uid = stmt_uid;
  return ref2;
}

/* Clone all references from SRC to DEST_NODE or DEST_VARPOOL_NODE.  */

void
ipa_clone_references (symtab_node *dest_node,
		      struct ipa_ref_list *src)
{
  struct ipa_ref *ref, *ref2;
  int i;
  for (i = 0; ipa_ref_list_reference_iterate (src, i, ref); i++)
    {
      bool speculative = ref->speculative;
      unsigned int stmt_uid = ref->lto_stmt_uid;

      ref2 = ipa_record_reference (dest_node,
				   ref->referred,
				   ref->use, ref->stmt);
      ref2->speculative = speculative;
      ref2->lto_stmt_uid = stmt_uid;
    }
}

/* Clone all referring from SRC to DEST_NODE or DEST_VARPOOL_NODE.  */

void
ipa_clone_referring (symtab_node *dest_node,
		    struct ipa_ref_list *src)
{
  struct ipa_ref *ref, *ref2;
  int i;
  for (i = 0; ipa_ref_list_referring_iterate (src, i, ref); i++)
    {
      bool speculative = ref->speculative;
      unsigned int stmt_uid = ref->lto_stmt_uid;

      ref2 = ipa_record_reference (ref->referring,
				   dest_node,
				   ref->use, ref->stmt);
      ref2->speculative = speculative;
      ref2->lto_stmt_uid = stmt_uid;
    }
}

/* Return true when execution of REF can lead to return from
   function. */
bool
ipa_ref_cannot_lead_to_return (struct ipa_ref *ref)
{
  return cgraph_node_cannot_return (ipa_ref_referring_node (ref));
}

/* Return true if list contains an alias.  */
bool
ipa_ref_has_aliases_p (struct ipa_ref_list *ref_list)
{
  struct ipa_ref *ref;
  int i;

  for (i = 0; ipa_ref_list_referring_iterate (ref_list, i, ref); i++)
    if (ref->use == IPA_REF_ALIAS)
      return true;
  return false;
}

/* Find the structure describing a reference in REFERRING_NODE to REFERRED_NODE
   and associated with statement STMT.  */

struct ipa_ref *
ipa_find_reference (symtab_node *referring_node, symtab_node *referred_node,
		    gimple stmt, unsigned int lto_stmt_uid)
{
  struct ipa_ref *r = NULL;
  int i;

  for (i = 0; ipa_ref_list_reference_iterate (&referring_node->ref_list, i, r); i++)
    if (r->referred == referred_node
	&& !r->speculative
	&& ((stmt && r->stmt == stmt)
	    || (lto_stmt_uid && r->lto_stmt_uid == lto_stmt_uid)
	    || (!stmt && !lto_stmt_uid && !r->stmt && !r->lto_stmt_uid)))
      return r;
  return NULL;
}

/* Remove all references from REFERRING_NODE that are associated with statement
   STMT.  */

void
ipa_remove_stmt_references (symtab_node *referring_node, gimple stmt)
{
  struct ipa_ref *r = NULL;
  int i = 0;

  while (ipa_ref_list_reference_iterate (&referring_node->ref_list, i, r))
    if (r->stmt == stmt)
      ipa_remove_reference (r);
    else
      i++;
}

/* Remove all stmt references in non-speculative references.
   Those are not maintained during inlining & clonning. 
   The exception are speculative references that are updated along
   with callgraph edges associated with them.  */

void
ipa_clear_stmts_in_references (symtab_node *referring_node)
{
  struct ipa_ref *r = NULL;
  int i;

  for (i = 0; ipa_ref_list_reference_iterate (&referring_node->ref_list, i, r); i++)
    if (!r->speculative)
      {
	r->stmt = NULL;
	r->lto_stmt_uid = 0;
      }
}