/* PRU target specific passes
   Copyright (C) 2017-2023 Free Software Foundation, Inc.
   Dimitar Dimitrov <dimitar@dinux.eu>
   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/>.  */
#define IN_TARGET_CODE 1
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "backend.h"
#include "context.h"
#include "tm.h"
#include "alias.h"
#include "symtab.h"
#include "tree.h"
#include "diagnostic-core.h"
#include "function.h"
#include "gimple.h"
#include "gimple-iterator.h"
#include "gimple-walk.h"
#include "gimple-expr.h"
#include "tree-pass.h"
#include "pru-protos.h"
namespace {
/* Scan the tree to ensure that the compiled code by GCC
   conforms to the TI ABI specification.  If GCC cannot
   output a conforming code, raise an error.  */
const pass_data pass_data_tiabi_check =
{
  GIMPLE_PASS, /* type */
  "*tiabi_check", /* name */
  OPTGROUP_NONE, /* optinfo_flags */
  TV_NONE, /* tv_id */
  PROP_gimple_any, /* properties_required */
  0, /* properties_provided */
  0, /* properties_destroyed */
  0, /* todo_flags_start */
  0, /* todo_flags_finish */
};
/* Implementation class for the TI ABI compliance-check pass.  */
class pass_tiabi_check : public gimple_opt_pass
{
public:
  pass_tiabi_check (gcc::context *ctxt)
    : gimple_opt_pass (pass_data_tiabi_check, ctxt)
  {}
  /* opt_pass methods: */
  virtual unsigned int execute (function *);
  virtual bool gate (function *fun ATTRIBUTE_UNUSED)
  {
    return pru_current_abi == PRU_ABI_TI;
  }
}; // class pass_tiabi_check
/* Return 1 if type TYPE is a pointer to function type or a
   structure having a pointer to function type as one of its fields.
   Otherwise return 0.  */
static bool
chkp_type_has_function_pointer (const_tree type)
{
  bool res = false;
  if (POINTER_TYPE_P (type) && FUNC_OR_METHOD_TYPE_P (TREE_TYPE (type)))
    res = true;
  else if (RECORD_OR_UNION_TYPE_P (type))
    {
      tree field;
      for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field))
	if (TREE_CODE (field) == FIELD_DECL)
	  res = res || chkp_type_has_function_pointer (TREE_TYPE (field));
    }
  else if (TREE_CODE (type) == ARRAY_TYPE)
    res = chkp_type_has_function_pointer (TREE_TYPE (type));
  return res;
}
/* Check the function declaration FNTYPE for TI ABI compatibility.  */
static void
chk_function_decl (const_tree fntype, location_t call_location)
{
  /* GCC does not check if the RETURN VALUE pointer is NULL,
     so do not allow GCC functions with large return values.  */
  if (!VOID_TYPE_P (TREE_TYPE (fntype))
      && pru_return_in_memory (TREE_TYPE (fntype), fntype))
    error_at (call_location,
	      "large return values not supported with %<-mabi=ti%> option");
  /* Check this function's arguments.  */
  for (tree p = TYPE_ARG_TYPES (fntype); p; p = TREE_CHAIN (p))
    {
      tree arg_type = TREE_VALUE (p);
      if (chkp_type_has_function_pointer (arg_type))
	error_at (call_location,
		  "function pointers not supported with %<-mabi=ti%> option");
    }
}
/* Callback for walk_gimple_seq that checks TP tree for TI ABI compliance.  */
static tree
check_op_callback (tree *tp, int *walk_subtrees, void *data)
{
  struct walk_stmt_info *wi = (struct walk_stmt_info *) data;
  if (RECORD_OR_UNION_TYPE_P (*tp) || TREE_CODE (*tp) == ENUMERAL_TYPE)
    {
      /* Forward declarations have NULL tree type.  Skip them.  */
      if (TREE_TYPE (*tp) == NULL)
	return NULL;
    }
  /* TODO - why C++ leaves INTEGER_TYPE forward declarations around?  */
  if (TREE_TYPE (*tp) == NULL)
    return NULL;
  const tree type = TREE_TYPE (*tp);
  /* Direct function calls are allowed, obviously.  */
  gcall *call = dyn_cast <gcall *> (gsi_stmt (wi->gsi));
  if (call
      && tp == gimple_call_fn_ptr (call)
      && gimple_call_fndecl (call))
    return NULL;
  switch (TREE_CODE (type))
    {
    case FUNCTION_TYPE:
    case METHOD_TYPE:
	{
	  /* Note: Do not enforce a small return value.  It is safe to
	     call any TI ABI function from GCC, since GCC will
	     never pass NULL.  */
	  /* Check arguments for function pointers.  */
	  for (tree p = TYPE_ARG_TYPES (type); p; p = TREE_CHAIN (p))
	    {
	      tree arg_type = TREE_VALUE (p);
	      if (chkp_type_has_function_pointer (arg_type))
		error_at (gimple_location (wi->stmt), "function pointers "
			  "not supported with %<-mabi=ti%> option");
	    }
	  break;
	}
    case RECORD_TYPE:
    case UNION_TYPE:
    case QUAL_UNION_TYPE:
    case POINTER_TYPE:
	{
	  if (chkp_type_has_function_pointer (type))
	    {
	      error_at (gimple_location (wi->stmt),
			"function pointers not supported with "
			"%<-mabi=ti%> option");
	      *walk_subtrees = false;
	    }
	  break;
	}
    default:
	  break;
    }
  return NULL;
}
/* Pass implementation.  */
unsigned
pass_tiabi_check::execute (function *fun)
{
  struct walk_stmt_info wi;
  const_tree fntype = TREE_TYPE (fun->decl);
  gimple_seq body = gimple_body (current_function_decl);
  memset (&wi, 0, sizeof (wi));
  wi.info = NULL;
  wi.want_locations = true;
  /* Check the function body.  */
  walk_gimple_seq (body, NULL, check_op_callback, &wi);
  /* Check the function declaration.  */
  chk_function_decl (fntype, fun->function_start_locus);
  return 0;
}
} // anon namespace
gimple_opt_pass *
make_pass_tiabi_check (gcc::context *ctxt)
{
  return new pass_tiabi_check (ctxt);
}
/* Register as early as possible.  */
void
pru_register_abicheck_pass (void)
{
  opt_pass *tiabi_check = make_pass_tiabi_check (g);
  struct register_pass_info tiabi_check_info
    = { tiabi_check, "*warn_unused_result",
	1, PASS_POS_INSERT_AFTER
      };
  register_pass (&tiabi_check_info);
}