[18/61] Add -mfunc-opt-list=<FILE>

Message ID 20250131171232.1018281-20-aleksandar.rakic@htecgroup.com
State New
Headers
Series Improve Mips target |

Commit Message

Aleksandar Rakic Jan. 31, 2025, 5:13 p.m. UTC
  From: Simon Dardis <simon.dardis@imgtec.com>

New option for MIPS -mfunc-opt-list=FILE. This option takes a file which
has one function per line followed by a whitespace (space/tab) followed
by one or more attributes. Supported attributes are O2, Os,
code-read=pcrel, always_inline, noinline, mips16, nomips16, epi,
longcall.

Attributes are applied to functions that the compiler sees, so functions
listed that the compiler doesn't see are ignored.

Now understands the majority of function attributes. These are:
O1, O2, O3, Os, mips16, nomips16, always_inline, noinline, unused, used,
far, near, hot, cold, code_readable, alias, aligned, alloc_size,
alloc_align, assume_aligned, artifical, constructor, const, deprecated,
destructor, error, flatten, gnu_inline, interrupt,
keep_interrupts_masked, long_call, leaf, noclone, noreturn, malloc,
nonnull, nothrow, optimize, returns_nonnull, returns_twice, section,
pure, use_debug_exception_return, use_shadow_register_set, visibility,
warning, warn_unused_result, weak, weakref.

Syntax of attributes that take arguments is like: alias ("O2")
or nonnull (1,2)

Attach unknown attributes anyway.

Cherry-picked e2ff99868adedb1a563ee69b3076838dd7ae4450
from https://github.com/MIPS/gcc

Signed-off-by: Simon Dardis <simon.dardis@imgtec.com>
Signed-off-by: Faraz Shahbazker <fshahbazker@wavecomp.com>
Signed-off-by: Aleksandar Rakic <aleksandar.rakic@htecgroup.com>
---
 gcc/config/mips/mips.cc  | 609 +++++++++++++++++++++++++++++++++++++++
 gcc/config/mips/mips.opt |   4 +
 gcc/doc/invoke.texi      |  33 +++
 3 files changed, 646 insertions(+)
  

Patch

diff --git a/gcc/config/mips/mips.cc b/gcc/config/mips/mips.cc
index 55d06b87c0d..32fe62ce79b 100644
--- a/gcc/config/mips/mips.cc
+++ b/gcc/config/mips/mips.cc
@@ -697,6 +697,524 @@  mips_find_list (const char *var, struct mips_sdata_entry *list)
   return false;
 }
 
+/* Argument type descriptor.  */
+
+enum mips_func_opt_list_arg_t
+{
+  FOL_ARG_NONE,
+  FOL_ARG_STRING,
+  FOL_ARG_SINGLE_NUM,
+  FOL_ARG_OPTIONAL_NUM_LIST,
+  FOL_ARG_NUM_ONE_OR_TWO,
+  FOL_ARG_OPTIONAL_STRING,
+  FOL_ARG_OPTIONAL_NUM,
+  FOL_ARG_UNKNOWN
+};
+
+/* Collisons for FUNC_OPT_LIST.  Rather that just relying on the middle to
+   complain, check at parse time so we can produce accurate diagnositics.  */
+
+enum mips_fol_collides
+{
+  FOLC_O1,
+  FOLC_O2,
+  FOLC_O3,
+  FOLC_OS,
+  FOLC_MIPS16,
+  FOLC_NOMIPS16,
+  FOLC_ALWAYS_INLINE,
+  FOLC_NOINLINE,
+  FOLC_UNUSED,
+  FOLC_USED,
+  FOLC_FAR,
+  FOLC_NEAR,
+  FOLC_HOT,
+  FOLC_COLD,
+  FOLC_END
+};
+
+/* Part of FUNC_OPT_LIST.  Use a tuple to record the name to be matched against
+   which GCC uses internally, an optional second string if the name is required
+   to be an argument of a different attribute and a bitmask describing which
+   other entries collide with this entry.  */
+
+struct attr_desc
+{
+  const char * optstring;
+  const char * maintype;
+  enum mips_func_opt_list_arg_t arg_type;
+  int collisions;
+};
+
+/* This table encodes the strings to match against for parsing func-opt-list,
+   an optional string which the first is argument of, e.g. optimize ("O2")
+   and the colliding attributes.  */
+
+static const struct attr_desc mips_func_opt_list_strings[] = {
+  {"O1",	"optimize", FOL_ARG_NONE,
+   1 << FOLC_O2 | 1 << FOLC_O3 | 1 << FOLC_OS },
+  {"O2",	"optimize", FOL_ARG_NONE,
+   1 << FOLC_O1 | 1 << FOLC_O3 | 1 << FOLC_OS },
+  {"O3",	"optimize", FOL_ARG_NONE,
+   1 << FOLC_O1 | 1 << FOLC_O2 | 1 << FOLC_OS },
+  {"Os",	"optimize", FOL_ARG_NONE,
+   1 << FOLC_O1 | 1 << FOLC_O2 | 1 << FOLC_O3 },
+  {"mips16",		 0, 	     FOL_ARG_NONE, 1 << FOLC_NOMIPS16 },
+  {"nomips16",		 0,	     FOL_ARG_NONE, 1 << FOLC_MIPS16 },
+  {"always_inline",	 0,	     FOL_ARG_NONE, 1 << FOLC_NOINLINE },
+  {"noinline",		 0,	     FOL_ARG_NONE, 1 << FOLC_ALWAYS_INLINE },
+  {"unused",		 0,	     FOL_ARG_NONE, 1 << FOLC_USED },
+  {"used",		 0,	     FOL_ARG_NONE, 1 << FOLC_UNUSED },
+  {"far",		 0,	     FOL_ARG_NONE, 1 << FOLC_NEAR },
+  {"near",		 0,	     FOL_ARG_NONE, 1 << FOLC_FAR },
+  {"hot",		 0,	     FOL_ARG_NONE, 1 << FOLC_COLD },
+  {"cold",		 0,	     FOL_ARG_NONE, 1 << FOLC_HOT },
+  {"code_readable",	 0,	     FOL_ARG_STRING, 0 },
+  {"alias",		 0,	     FOL_ARG_STRING, 0 },
+  {"aligned",		 0,	     FOL_ARG_SINGLE_NUM, 0},
+  {"alloc_size",	 0,	     FOL_ARG_NUM_ONE_OR_TWO, 0},
+  {"alloc_align",	 0,	     FOL_ARG_SINGLE_NUM, 0},
+  {"assume_aligned",	 0,	     FOL_ARG_NUM_ONE_OR_TWO, 0},
+  {"artifical",		 0,	     FOL_ARG_NONE, 0 },
+  {"constructor",	 0,	     FOL_ARG_OPTIONAL_NUM, 0},
+  {"const",		 0,	     FOL_ARG_NONE, 0 },
+  {"deprecated",	 0,	     FOL_ARG_OPTIONAL_STRING, 0},
+  {"destructor",	 0,	     FOL_ARG_OPTIONAL_NUM, 0},
+  {"error",		 0,	     FOL_ARG_OPTIONAL_STRING, 0},
+  {"flatten",		 0,	     FOL_ARG_NONE, 0 },
+  {"gnu_inline",	 0,	     FOL_ARG_NONE, 0 },
+  {"interrupt",		 0,	     FOL_ARG_NONE, 0 },
+  {"keep_interrupts_masked", 0,	     FOL_ARG_NONE, 0 },
+  {"long_call",		 0,	     FOL_ARG_NONE, 0 },
+  {"leaf",		 0,	     FOL_ARG_NONE, 0 },
+  {"noclone",		 0,	     FOL_ARG_NONE, 0 },
+  {"noreturn",		 0,	     FOL_ARG_NONE, 0 },
+  {"malloc",		 0,	     FOL_ARG_NONE, 0 },
+  {"nonnull",		 0,	     FOL_ARG_OPTIONAL_NUM_LIST, 0 },
+  {"nothrow",		 0,	     FOL_ARG_NONE, 0 },
+  {"optimize",		 0,	     FOL_ARG_STRING, 0 },
+  {"returns_nonnull",	 0,	     FOL_ARG_NONE, 0 },
+  {"returns_twice",	 0,	     FOL_ARG_NONE, 0 },
+  {"section",		 0,	     FOL_ARG_STRING, 0 },
+  {"pure",		 0,	     FOL_ARG_NONE, 0 },
+  {"use_debug_exception_return", 0,  FOL_ARG_NONE, 0 },
+  {"use_shadow_register_set", 0,     FOL_ARG_NONE, 0 },
+  {"visibility",	 0,	     FOL_ARG_STRING, 0 },
+  {"warning",		 0,	     FOL_ARG_OPTIONAL_STRING, 0 },
+  {"warn_unused_result", 0,	     FOL_ARG_NONE, 0 },
+  {"weak",		 0,	     FOL_ARG_NONE, 0 },
+  {"weakref",		 0,	     FOL_ARG_NONE, 0 },
+  /* End of table marker, FOL_ARG_NONE is required to stop the attribute
+     argument parsing.  */
+  {"\0",		 0,	     FOL_ARG_UNKNOWN, 0 },
+};
+
+/* Argument list...  */
+
+struct GTY ((chain_next ("%h.next"))) mips_func_opt_list_arg
+{
+  char * GTY ((skip (""))) arg;
+  unsigned int optimization;
+  mips_func_opt_list_arg_t arg_type;
+  struct mips_func_opt_list_arg * next;
+};
+
+/* Unknown attribute list.  */
+
+struct GTY ((chain_next ("%h.next"))) mips_func_opt_unknown_list
+{
+  char * GTY ((skip (""))) attribute;
+  struct mips_func_opt_list_arg * args;
+  struct mips_func_opt_unknown_list * next;
+};
+
+/* ... For this.  */
+
+struct GTY ((chain_next ("%h.next"))) mips_func_opt_list
+{
+  char * GTY ((skip (""))) func_name;
+  sbitmap attributes;
+  struct mips_func_opt_unknown_list * unknowns;
+  struct mips_func_opt_list_arg * args;
+  struct mips_func_opt_list * next;
+};
+
+/* Head of the function optimization list.  */
+
+static struct GTY ((chain_next ("%h.next")))
+mips_func_opt_list * mips_fn_opt_list;
+
+/* Search the func-opt-list for func's entry and return it.  */
+
+static struct mips_func_opt_list *
+mips_func_opt_list_find (const char * func)
+{
+  struct mips_func_opt_list * i;
+  for (i = mips_fn_opt_list; i != NULL; i = i->next)
+    if (strcmp (i->func_name, func) == 0)
+      return i;
+
+  return NULL;
+}
+
+/* Check if OPT conflicts with the current attributes of f.  Return
+   the entry corresponding the existing attribute that conflicts.  */
+
+static int
+mips_fol_attr_conflicts (struct mips_func_opt_list * f,
+			unsigned int opt)
+{
+  /* Trival case: opt has no conflicting attrs / leave for the middle end to
+     diagnose.  */
+  if (opt > FOLC_END)
+    return 0;
+
+  for (int i = 0; i < FOLC_END; i++)
+    if (bitmap_bit_p (f->attributes, i)
+	&& ((1 << i) & mips_func_opt_list_strings[opt].collisions))
+      return i;
+
+  return 0;
+}
+
+#define MATCH_WHITESPACE(A) ISSPACE (A)
+#define MATCH_EMPTYSTRING(A) (A == '\n' || A == 0 || A == EOF)
+
+/* Subroutinue for below.  */
+
+static void
+mips_func_opt_list_parse_arg_1 (const char * line,
+				struct mips_func_opt_list * f,
+				unsigned int pos,
+				unsigned int length,
+				unsigned int opt)
+{
+  struct mips_func_opt_list_arg * arg
+    = (struct mips_func_opt_list_arg *)xcalloc (1,
+				       sizeof (struct mips_func_opt_list_arg));
+
+  arg->arg = xstrndup (&(line[pos]), length);
+  arg->optimization = opt;
+  arg->next = f->args;
+
+  if (mips_func_opt_list_strings[opt].optstring == 0)
+    f->unknowns->args = arg;
+  else
+    f->args = arg;
+}
+
+/* Parse an argument of an attribute of the form:
+   ("string") or (N<,N,N,...>)
+   and attach it to the passed struct mips_func_opt_list.
+   Return the position: either just after ')' or the start of next
+   word.  */
+
+static unsigned int
+mips_func_opt_list_parse_arg (const char * line, struct mips_func_opt_list * f,
+			      unsigned int opt, unsigned int pos,
+			      const char * file, int lineno)
+{
+  enum mips_func_opt_list_arg_t arg_type;
+  arg_type = mips_func_opt_list_strings[opt].arg_type;
+  unsigned int length = 0;
+  unsigned int arg_count = 0;
+
+  if (arg_type == FOL_ARG_NONE)
+    return pos;
+
+  /* Match "<whitespace>+("  */
+  while (MATCH_WHITESPACE (line[pos]))
+    pos++;
+
+  if (MATCH_EMPTYSTRING (line[pos])
+      || line[pos] != '(')
+    {
+      if (line[pos] != '('
+	  && (arg_type == FOL_ARG_OPTIONAL_NUM_LIST
+	      || arg_type == FOL_ARG_OPTIONAL_STRING
+	      || arg_type == FOL_ARG_OPTIONAL_NUM
+	      || arg_type == FOL_ARG_UNKNOWN))
+	return pos;
+      else
+	error ("%s:%d:%d: Expected '('", file, lineno, pos);
+    }
+
+  pos += 1;
+  while (MATCH_WHITESPACE (line[pos])
+	 && !MATCH_EMPTYSTRING (line[pos]))
+    pos++;
+
+  /* Handle the case of an unknown optimization.  Despite not knowing the
+     format of the arguments, we assume its either a string or an list of
+     numbers.  Peek at the input to correct arg_type.  */
+
+  if (arg_type == FOL_ARG_UNKNOWN)
+    {
+      if (line[pos] == '\"')
+	arg_type = FOL_ARG_STRING;
+      else
+	arg_type = FOL_ARG_OPTIONAL_NUM_LIST;
+    }
+
+  switch (arg_type)
+  {
+    /* Parse something of the form ("<string>") and add <string> to e->args.  */
+    case FOL_ARG_OPTIONAL_STRING:
+    case FOL_ARG_STRING:
+      if (MATCH_EMPTYSTRING (line[pos])
+	  || line[pos] != '\"')
+	/* Unexpected line end.  */
+	error ("%s:%d:%d: Expected '\"'", file, lineno, pos);
+
+      pos += 1;
+
+      while (!MATCH_EMPTYSTRING (line[pos+length])
+	     && line[pos+length] != '\"')
+	length += 1;
+
+      mips_func_opt_list_parse_arg_1 (line, f, pos, length, opt);
+
+      pos += length + 1;
+      break;
+
+    /* Parse something of the form (N<,N,N,N,..>) and add them individually
+       to e->args.  */
+    case FOL_ARG_SINGLE_NUM:
+    case FOL_ARG_OPTIONAL_NUM:
+    case FOL_ARG_OPTIONAL_NUM_LIST:
+    case FOL_ARG_NUM_ONE_OR_TWO:
+      while (1)
+	{
+	  while (ISDIGIT (line[pos+length]))
+	    length++;
+
+	  mips_func_opt_list_parse_arg_1 (line, f, pos, length, opt);
+	  pos += length;
+	  length = 0;
+	  arg_count++;
+
+	  while (MATCH_WHITESPACE (line[pos]))
+	    pos++;
+
+	  if (MATCH_EMPTYSTRING (line[pos])
+	      && (line[pos] != ','
+		  || line[pos] != ')'))
+	    error ("%s:%d:%d: Expected ',' or ')'", file, lineno, pos);
+
+	  if (line[pos] == ','
+	      && (arg_type == FOL_ARG_SINGLE_NUM
+		  || (arg_type == FOL_ARG_NUM_ONE_OR_TWO
+		      && arg_count > 2)))
+	    error ("%s:%d:%d: Unexpected ','", file, lineno, pos);
+
+	  if (line[pos] == ')')
+	    break;
+
+	  pos += 1;
+	  while (MATCH_WHITESPACE (line[pos]))
+	    pos++;
+	}
+      break;
+    default:
+      gcc_unreachable ();
+  }
+
+  while (MATCH_WHITESPACE (line[pos])
+	 && !MATCH_EMPTYSTRING (line[pos]))
+   pos++;
+
+  /* Unmatched ')'.  */
+  if (MATCH_EMPTYSTRING (line[pos])
+     || line[pos] != ')')
+  error ("%s:%d:%d: Expected ')'", file, lineno, pos);
+
+  pos += 1;
+
+  return pos;
+}
+
+/* Read a line and return a struct describing attributes for the function.
+   Odd case: If the function has already appeared in mips_fn_opt_list update
+   the entry in place but return NULL, otherwise we may end up building a
+   circular list.  Error out nicely when: misspelled optimization, conflicting
+   attributes.  */
+
+static struct mips_func_opt_list *
+mips_func_opt_list_read_line (const char * line, const char * file, int lineno)
+{
+  size_t identifier_length = 0;
+  unsigned int opt = 0;
+  struct mips_func_opt_list * fl = NULL;
+  unsigned int pos = 0;
+  bool matched = false;
+  bool update = false;
+
+  /* Take all leading whitespace.  */
+  while (MATCH_WHITESPACE (line[pos]))
+    pos++;
+
+  if (MATCH_EMPTYSTRING (line[pos]))
+      return NULL;
+
+  /* Take all non-whitespace for the function name.  */
+  while (!MATCH_WHITESPACE (line[pos + identifier_length])
+	 && !MATCH_EMPTYSTRING (line[pos + identifier_length]))
+    identifier_length++;
+
+  /* Construct a new struct mips_func_opt_list temporarily and search for an
+     existing entry.  Use existing entry over a new one.  */
+  fl = (struct mips_func_opt_list *)
+	xcalloc (1, sizeof (struct mips_func_opt_list));
+  fl->func_name = xstrndup (&line[pos], identifier_length);
+  if (mips_func_opt_list_find (fl->func_name) == NULL)
+    {
+      fl->args = NULL;
+      fl->unknowns = NULL;
+      fl->next = NULL;
+      fl->attributes = sbitmap_alloc (sizeof (mips_func_opt_list_strings)
+				      / sizeof (struct attr_desc));
+      bitmap_clear (fl->attributes);
+    }
+  else
+    {
+      char * n = fl->func_name;
+      free (fl);
+      fl = mips_func_opt_list_find (n);
+      free (n);
+      update = true;
+    }
+
+  pos += identifier_length;
+  identifier_length = 0;
+
+  /* Warn for <func name> <empty string>  */
+  if (MATCH_EMPTYSTRING (line[pos]))
+    {
+      warning (OPT_Wattributes, "%s:%d: No optimizations specified for %qs",
+	       file, lineno, fl->func_name);
+      return NULL;
+    }
+
+  /* Parse a (possibly empty) list of attributes.  */
+  while (1)
+  {
+    while (MATCH_WHITESPACE (line[pos]))
+      pos++;
+
+    if (MATCH_EMPTYSTRING (line[pos]))
+      {
+	if (update)
+	  return NULL;
+	else
+	  return fl;
+      }
+
+    /* Parse and match an attribute.  Warn and blame -mfunc-opt-list= if
+       the attributes are known to conflict.  The middle-end will complain as
+       well, but won't be able to blame the source properly.  */
+    while (!MATCH_WHITESPACE (line[pos + identifier_length])
+	   && !MATCH_EMPTYSTRING (line[pos + identifier_length])
+	   && line[pos + identifier_length] != '(')
+      identifier_length++;
+
+    for (opt = 0; *mips_func_opt_list_strings[opt].optstring != 0; opt++)
+      {
+	if (mips_func_opt_list_strings[opt].optstring != 0
+	    && strncmp (mips_func_opt_list_strings[opt].optstring, &(line[pos]),
+			identifier_length) == 0)
+	  {
+	    int conflict_attr = mips_fol_attr_conflicts (fl, opt);
+	    if (conflict_attr)
+	      warning (OPT_Wattributes, "%s:%d:%d: Attribute %qs cannot be"
+		       " applied to function %qs as it has the conflicting "
+		       "attribute %qs", file, lineno, pos,
+		       mips_func_opt_list_strings[opt].optstring,
+		       fl->func_name,
+		       mips_func_opt_list_strings[conflict_attr].optstring);
+
+	    bitmap_set_bit (fl->attributes, opt);
+	    matched = true;
+	    break;
+	  }
+      }
+
+    /* Correctly blame the unknown attribute.  */
+    if (!matched)
+      {
+	struct mips_func_opt_unknown_list * e
+	 = (struct mips_func_opt_unknown_list*)
+	     xcalloc (1, sizeof (struct mips_func_opt_unknown_list));
+	e->next = fl->unknowns;
+	e->attribute = xstrndup (&(line[pos]), identifier_length);
+	warning (OPT_Wattributes, "%s:%d:%d: Unknown attribute %qs for %qs",
+		 file, lineno, pos, e->attribute, fl->func_name);
+	fl->unknowns = e;
+      }
+
+    matched = false;
+    pos += identifier_length;
+    identifier_length = 0;
+
+    /* Parse any arguments if required, get new position.  */
+    pos = mips_func_opt_list_parse_arg (line, fl, opt, pos, file, lineno);
+  }
+}
+
+#undef MATCH_WHITESPACE
+#undef MATCH_EMPTYSTRING
+
+/* Entry point for FUNC_OPT_LIST.  Grab the conents of a file and build
+   a list of functions with a bitmap describing attributes desired.  */
+
+static void
+mips_func_opt_list_read ()
+{
+  FILE * fd;
+  int lineno = 0;
+  char line[512];
+  struct mips_func_opt_list *trial = NULL;
+
+  unsigned int i;
+  cl_deferred_option *opt;
+  vec<cl_deferred_option> *v;
+  v = (vec<cl_deferred_option> *) mips_func_opt_list_file;
+
+  FOR_EACH_VEC_ELT (*v, i, opt)
+    {
+      if (opt->opt_index != OPT_mfunc_opt_list_)
+	continue;
+
+      const char * filename = opt->arg;
+      if (filename == NULL)
+	continue;
+
+      fd = fopen (filename, "r");
+      if (fd == NULL)
+	{
+	  error ("Cannot read %qs for -mfunc-opt-list=\n", filename);
+	  return;
+	}
+
+      while (fgets (line, sizeof (line), fd))
+	{
+	  trial = mips_func_opt_list_read_line ((const char *)&line,
+						filename, lineno);
+	  lineno++;
+
+	  /* trial can be null if we didn't read a well formed line or if an
+	     update in place occurred.  Otherwise insert new entry at the head
+	     of the list.  */
+	  if (trial)
+	    {
+	      trial->next = mips_fn_opt_list;
+	      mips_fn_opt_list = trial;
+	    }
+	}
+
+      fclose (fd);
+    }
+}
+
 /* A table describing all the processors GCC knows about; see
    mips-cpus.def for details.  */
 static const struct mips_cpu_info mips_cpu_info_table[] = {
@@ -1569,6 +2087,86 @@  mips_comp_type_attributes (const_tree type1, const_tree type2)
   return 1;
 }
 
+/* Return a tree of the arguments of an attribute list.  */
+
+static tree
+mips_insert_fol_args (struct mips_func_opt_list_arg * args,
+		      unsigned int optimization,
+		      mips_func_opt_list_arg_t arg_type)
+{
+  tree ret = NULL;
+  for (; args; args = args->next)
+      if (args->optimization == optimization)
+	{
+	  tree arg;
+	  if (arg_type == FOL_ARG_OPTIONAL_STRING
+	      || arg_type == FOL_ARG_STRING)
+	    arg = build_string (strlen (args->arg), args->arg);
+	  else
+	    arg = build_int_cst (NULL, atoi (args->arg));
+
+	  if (ret == NULL)
+	    ret = build_tree_list (NULL_TREE, arg);
+	  else
+	    ret = chainon (ret, build_tree_list (NULL_TREE, arg));
+	}
+  return ret;
+}
+
+/* Implement -mfunc-opt-list.  With the struct of optmizations and attributes,
+   insert the attributes.  */
+
+static void
+mips_insert_fol_attributes (struct mips_func_opt_list * func_opt_list,
+			   tree *attributes)
+{
+  for (int i = 0; *mips_func_opt_list_strings[i].optstring != 0; i++)
+    if (bitmap_bit_p (func_opt_list->attributes, i))
+      {
+	const char * opstr = mips_func_opt_list_strings[i].optstring;
+	const char * maintype = mips_func_opt_list_strings[i].maintype;
+	int l = strlen (opstr);
+
+	tree attr_args = NULL;
+	if (mips_func_opt_list_strings[i].arg_type != FOL_ARG_NONE)
+	    attr_args = mips_insert_fol_args (func_opt_list->args, i,
+					mips_func_opt_list_strings[i].arg_type);
+
+	/* Some strange logic:  If .maintype == 0, .optstring is the
+	   attribute.  Otherwise the actual attribute is
+	   .maintype (".optstring").  */
+	if (mips_func_opt_list_strings[i].maintype == 0)
+	  {
+	      *attributes = tree_cons (get_identifier (opstr), attr_args,
+				       *attributes);
+	  }
+	else
+	  {  attr_args = build_tree_list (NULL_TREE,
+					  build_string (l, opstr));
+
+	    *attributes = tree_cons (get_identifier (maintype), attr_args,
+				     *attributes);
+	  }
+      }
+
+  if (func_opt_list->unknowns)
+    {
+      struct mips_func_opt_unknown_list	* l = func_opt_list->unknowns;
+      while (l)
+	{
+	  tree attr_args = NULL;
+	  if (l->args)
+	    attr_args = mips_insert_fol_args (l->args, l->args->optimization,
+						 l->args->arg_type);
+
+	  *attributes = tree_cons (get_identifier (l->attribute), attr_args,
+				   *attributes);
+	  l = l->next;
+	}
+
+    }
+}
+
 /* Implement TARGET_INSERT_ATTRIBUTES.  */
 
 static void
@@ -1631,6 +2229,14 @@  mips_insert_attributes (tree decl, tree *attributes)
 	error ("%qE cannot have both %qs and %qs attributes",
 	       DECL_NAME (decl), "mips16", "micromips");
 
+      if (mips_fn_opt_list)
+	{
+	  struct mips_func_opt_list * func_opt_list
+	    = mips_func_opt_list_find (IDENTIFIER_POINTER (DECL_NAME (decl)));
+	  if (func_opt_list)
+	    mips_insert_fol_attributes (func_opt_list, attributes);
+	}
+
       if (TARGET_FLIP_MIPS16
 	  && !DECL_ARTIFICIAL (decl)
 	  && compression_flags == 0
@@ -20712,6 +21318,9 @@  mips_option_override (void)
   mips_unique_sections_list = mips_read_list (mips_unique_sections_file,
 					      "-munique-sections");
 
+  if (mips_func_opt_list_file)
+    mips_func_opt_list_read ();
+
   /* Set the small data limit.  */
   mips_small_data_threshold = (OPTION_SET_P (g_switch_value)
 			       ? g_switch_value
diff --git a/gcc/config/mips/mips.opt b/gcc/config/mips/mips.opt
index e0a305aec22..012ca91560f 100644
--- a/gcc/config/mips/mips.opt
+++ b/gcc/config/mips/mips.opt
@@ -552,3 +552,7 @@  msdata-opt-list=FILE    Use to specify variables to go in the .sdata section.
 munique-sections=
 Target RejectNegative Joined Var(mips_unique_sections_file)
 munique-sections=FILE   Use to specify sections that should be made unique.
+
+mfunc-opt-list=
+Target RejectNegative Joined Var(mips_func_opt_list_file) Init(0) Defer
+mfunc-opt-list=FILE	Use to specify per function optimizations.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 952baf872ec..cd84cafafd5 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -1147,6 +1147,7 @@  Objective-C and Objective-C++ Dialects}.
 -msmartmips  -mno-smartmips
 -mpaired-single  -mno-paired-single  -mdmx  -mno-mdmx
 -mips3d  -mno-mips3d  -mmt  -mno-mt  -mllsc  -mno-llsc
+-mfunc-opt-list=@var{file}
 -mlong64  -mlong32  -msym32  -mno-sym32
 -G@var{num}  -mlocal-sdata  -mno-local-sdata
 -mextern-sdata  -mno-extern-sdata  -mgpopt  -mno-gopt
@@ -28709,6 +28710,38 @@  Use (do not use) the MIPS Loongson EXTensions (EXT) instructions.
 @itemx -mno-loongson-ext2
 Use (do not use) the MIPS Loongson EXTensions r2 (EXT2) instructions.
 
+@opindex mfunc-opt-list
+@item -mfunc-opt-list=
+Read a list of functions from a file and apply a series of attributes to
+them as if they had been specified in the source file.  The format of the
+file is plain text with one function per line.  The first non-whitespace
+word of each line is the function name, followed by whitespace, then a
+whitespace separated list of function attributes.  Attributes are written
+in the same format as they appear in the parentheses of the __attribute__
+directive.
+
+For example:
+@smallexample
+foo   mips16 optimize ("Os")
+bar   always_inline
+@end smallexample
+
+The following attributes are recognised:
+@smallexample
+alias, aligned, alloc_size, alloc_align, always_inline, artifical,
+assume_aligned, code_readable, cold, const, constructor, deprecated,
+destructor, error, far, flatten, gnu_inline, hot, interrupt,
+keep_interrupts_masked, leaf, long_call, malloc, mips16, near, noclone,
+noinline, nomips16, nonnull, noreturn, nothrow, optimize, pure,
+returns_nonnull, returns_twice, section, unused, used,
+use_debug_exception_return, use_shadow_register_set, visibility, warning,
+warn_unused_result, weak, weakref.
+@end smallexample
+
+Unrecognised attributes will produce a warning diagnostic giving the location
+where it was found in the list.  A second diagnostic will be produced citing
+the name of the function and unknown attribute.
+
 @opindex mlong64
 @item -mlong64
 Force @code{long} types to be 64 bits wide.  See @option{-mlong32} for