diff --git a/gcc/config.gcc b/gcc/config.gcc
index cc37a9c768d..4c3d5cb2c8a 100644
--- a/gcc/config.gcc
+++ b/gcc/config.gcc
@@ -545,7 +545,7 @@ riscv*)
 	extra_objs="riscv-builtins.o riscv-c.o riscv-sr.o riscv-shorten-memrefs.o riscv-selftests.o riscv-string.o"
 	extra_objs="${extra_objs} riscv-v.o riscv-vsetvl.o riscv-vector-costs.o"
 	extra_objs="${extra_objs} riscv-vector-builtins.o riscv-vector-builtins-shapes.o riscv-vector-builtins-bases.o"
-	extra_objs="${extra_objs} thead.o"
+	extra_objs="${extra_objs} thead.o riscv-target-attr.o"
 	d_target_objs="riscv-d.o"
 	extra_headers="riscv_vector.h"
 	target_gtfiles="$target_gtfiles \$(srcdir)/config/riscv/riscv-vector-builtins.cc"
diff --git a/gcc/config/riscv/riscv-opts.h b/gcc/config/riscv/riscv-opts.h
index e7c6b75c5ea..31ee42dea6b 100644
--- a/gcc/config/riscv/riscv-opts.h
+++ b/gcc/config/riscv/riscv-opts.h
@@ -112,6 +112,12 @@ enum riscv_entity
    ? 0 \
    : 32 << (__builtin_popcount (riscv_zvl_flags) - 1))
 
+/* Same as TARGET_MIN_VLEN, but take an OPTS as gcc_options.  */
+#define TARGET_MIN_VLEN_OPTS(opts)                                             \
+  ((opts->x_riscv_zvl_flags == 0)                                              \
+     ? 0                                                                       \
+     : 32 << (__builtin_popcount (opts->x_riscv_zvl_flags) - 1))
+
 /* We only enable VLS modes for VLA vectorization since fixed length VLMAX mode
    is the highest priority choice and should not conflict with VLS modes.  */
 #define TARGET_VECTOR_VLS                                                      \
diff --git a/gcc/config/riscv/riscv-protos.h b/gcc/config/riscv/riscv-protos.h
index 43426a5326b..eb23eb1e78f 100644
--- a/gcc/config/riscv/riscv-protos.h
+++ b/gcc/config/riscv/riscv-protos.h
@@ -103,6 +103,7 @@ extern void riscv_split_doubleword_move (rtx, rtx);
 extern const char *riscv_output_move (rtx, rtx);
 extern const char *riscv_output_return ();
 extern void riscv_declare_function_name (FILE *, const char *, tree);
+extern void riscv_declare_function_size (FILE *, const char *, tree);
 extern void riscv_asm_output_alias (FILE *, const tree, const tree);
 extern void riscv_asm_output_external (FILE *, const tree, const char *);
 extern bool
@@ -579,5 +580,25 @@ th_mempair_output_move (rtx[4], bool, machine_mode, RTX_CODE);
 
 extern bool riscv_use_divmod_expander (void);
 void riscv_init_cumulative_args (CUMULATIVE_ARGS *, tree, rtx, tree, int);
+extern bool
+riscv_option_valid_attribute_p (tree, tree, tree, int);
+extern void
+riscv_override_options_internal (struct gcc_options *);
+
+struct riscv_tune_param;
+/* Information about one micro-arch we know about.  */
+struct riscv_tune_info {
+  /* This micro-arch canonical name.  */
+  const char *name;
+
+  /* Which automaton to use for tuning.  */
+  enum riscv_microarchitecture_type microarchitecture;
+
+  /* Tuning parameters for this micro-arch.  */
+  const struct riscv_tune_param *tune_param;
+};
+
+const struct riscv_tune_info *
+riscv_parse_tune (const char *, bool);
 
 #endif /* ! GCC_RISCV_PROTOS_H */
diff --git a/gcc/config/riscv/riscv-target-attr.cc b/gcc/config/riscv/riscv-target-attr.cc
new file mode 100644
index 00000000000..33cff2c222f
--- /dev/null
+++ b/gcc/config/riscv/riscv-target-attr.cc
@@ -0,0 +1,395 @@
+/* Subroutines used for parsing target attribute for RISC-V.
+   Copyright (C) 2023 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/>.  */
+
+#define IN_TARGET_CODE 1
+
+#define INCLUDE_STRING
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "target.h"
+#include "tree.h"
+#include "tm_p.h"
+#include "diagnostic.h"
+#include "opts.h"
+#include "riscv-subset.h"
+
+namespace {
+class riscv_target_attr_parser
+{
+public:
+  riscv_target_attr_parser (location_t loc)
+    : m_found_arch_p (false)
+    , m_found_tune_p (false)
+    , m_found_cpu_p (false)
+    , m_subset_list (nullptr)
+    , m_loc (loc)
+    , m_cpu_info (nullptr)
+    , m_tune (nullptr)
+  {
+  }
+
+  bool handle_arch (const char *);
+  bool handle_cpu (const char *);
+  bool handle_tune (const char *);
+
+  void set_loc (location_t loc) {
+    m_loc = loc;
+  }
+
+  void update_settings (struct gcc_options *opts) const;
+private:
+  const char *m_raw_attr_str;
+  bool parse_arch (const char *);
+
+  bool m_found_arch_p;
+  bool m_found_tune_p;
+  bool m_found_cpu_p;
+  riscv_subset_list *m_subset_list;
+  location_t m_loc;
+  const  riscv_cpu_info *m_cpu_info;
+  const char *m_tune;
+};
+}
+
+/* All the information needed to handle a target attribute.
+   NAME is the name of the attribute.
+   HANDLER is the function that takes the attribute string as an argument.  */
+
+struct riscv_attribute_info
+{
+  const char *name;
+  bool (riscv_target_attr_parser::*handler) (const char *);
+};
+
+/* The target attributes that we support.  */
+
+static const struct riscv_attribute_info riscv_attributes[]
+  = {{"arch", &riscv_target_attr_parser::handle_arch},
+     {"cpu", &riscv_target_attr_parser::handle_cpu},
+     {"tune", &riscv_target_attr_parser::handle_tune}};
+
+bool
+riscv_target_attr_parser::parse_arch (const char *str)
+{
+  if (m_subset_list)
+    delete m_subset_list;
+  /* Check if it's setting full arch string.  */
+  if (strncmp ("rv", str, strlen ("rv")) == 0)
+    {
+      m_subset_list = riscv_subset_list::parse (str, location_t ());
+
+      if (m_subset_list == nullptr)
+	goto fail;
+
+      return true;
+    }
+  else
+    {
+      /* Parsing the extension list like "+<ext>[,+<ext>]*".  */
+      size_t len = strlen (str);
+      char *str_to_check = (char *) alloca (len + 1);
+      strcpy (str_to_check, str);
+      const char *token = strtok_r (str_to_check, ",", &str_to_check);
+      m_subset_list = riscv_current_subset_list ()->clone ();
+      m_subset_list->set_loc (m_loc);
+      while (token)
+	{
+	  if (token[0] != '+')
+	    {
+	      error_at (
+		m_loc,
+		"unexpected arch for %<target()%> attribute: must start "
+		"with + or rv");
+	      goto fail;
+	    }
+	  else
+	    {
+	      const char *result = m_subset_list->parse_single_ext (token + 1);
+	      /* Check parse_single_ext has consume all string.  */
+	      if (*result != '\0')
+		{
+		  error_at (
+		    m_loc,
+		    "unexpected arch for %<target()%> attribute: bad "
+		    "string found %<%s%>", token);
+		  goto fail;
+		}
+	    }
+	  token = strtok_r (NULL, ",", &str_to_check);
+	}
+
+      return true;
+    }
+fail:
+  if (m_subset_list != nullptr)
+    {
+      delete m_subset_list;
+      m_subset_list = nullptr;
+    }
+  return false;
+}
+
+/* Handle the ARCH_STR argument to the arch= target attribute.  */
+
+bool
+riscv_target_attr_parser::handle_arch (const char *str)
+{
+  if (m_found_arch_p)
+    warning_at (m_loc, 0,
+		"%<target()%> attribute: arch appears more than once, the "
+		"settings of the last one will be used");
+  m_found_arch_p = true;
+  return parse_arch (str);
+}
+
+/* Handle the CPU_STR argument to the cpu= target attribute.  */
+
+bool
+riscv_target_attr_parser::handle_cpu (const char *str)
+{
+  if (m_found_cpu_p)
+    warning_at (m_loc, 0,
+		"%<target()%> attribute: cpu appears more than once, the "
+		"settings of the last one will be used");
+
+  m_found_cpu_p = true;
+  const riscv_cpu_info *cpu_info = riscv_find_cpu (str);
+
+  if (!cpu_info)
+    {
+      error_at (m_loc, "%<target()%> attribute: unknown CPU %qs", str);
+      return false;
+    }
+
+  if (m_subset_list == nullptr)
+    {
+      const char *arch_str = cpu_info->arch;
+      m_subset_list = riscv_subset_list::parse (arch_str, m_loc);
+      gcc_assert (m_subset_list);
+    }
+
+  m_cpu_info = cpu_info;
+  return true;
+}
+
+/* Handle the TUNE_STR argument to the tune= target attribute.  */
+
+bool
+riscv_target_attr_parser::handle_tune (const char *str)
+{
+  if (m_found_tune_p)
+    warning_at (m_loc, 0,
+		"%<target()%> attribute: tune appears more than once, the "
+		"settings of the last one will be used");
+  m_found_tune_p = true;
+  const struct riscv_tune_info *tune = riscv_parse_tune (str, true);
+
+  if (tune == nullptr)
+    {
+      error_at (m_loc, "%<target()%> attribute: unknown TUNE %qs", str);
+      return false;
+    }
+
+  m_tune = tune->name;
+
+  return true;
+}
+
+void
+riscv_target_attr_parser::update_settings (struct gcc_options *opts) const
+{
+  if (m_subset_list)
+    riscv_set_arch_by_subset_list (m_subset_list, opts);
+
+  if (m_cpu_info)
+    opts->x_riscv_cpu_string = m_cpu_info->name;
+
+  if (m_tune)
+    opts->x_riscv_tune_string = m_tune;
+  else
+    {
+      if (m_cpu_info)
+	opts->x_riscv_tune_string = m_cpu_info->tune;
+    }
+}
+
+/* Parse ARG_STR which contains the definition of one target attribute.
+   Show appropriate errors if any or return true if the attribute is valid.  */
+
+static bool
+riscv_process_one_target_attr (char *arg_str,
+			       location_t loc,
+			       riscv_target_attr_parser &attr_parser)
+{
+  size_t len = strlen (arg_str);
+
+  if (len == 0)
+    {
+      error_at (loc, "malformed %<target()%> attribute");
+      return false;
+    }
+
+  char *str_to_check = (char *) alloca (len + 1);
+  strcpy (str_to_check, arg_str);
+
+  char *arg = strchr (str_to_check, '=');
+
+  if (!arg)
+    {
+      error_at (
+	loc,
+	"attribute %<target(\"%s\")%> does not accept an argument",
+	str_to_check);
+      return false;
+    }
+
+  arg[0] = '\0';
+  ++arg;
+  for (const auto &attr : riscv_attributes)
+    {
+      /* If the names don't match up, or the user has given an argument
+	 to an attribute that doesn't accept one, or didn't give an argument
+	 to an attribute that expects one, fail to match.  */
+      if (strncmp (str_to_check, attr.name, strlen (attr.name)) != 0)
+	continue;
+
+      return (&attr_parser->*attr.handler) (arg);
+    }
+  error_at (loc, "Got unknown attribute %<target(\"%s\")%>", str_to_check);
+
+  return false;
+}
+
+/* Count how many times the character C appears in
+   NULL-terminated string STR.  */
+
+static unsigned int
+num_occurences_in_str (char c, char *str)
+{
+  unsigned int res = 0;
+  while (*str != '\0')
+    {
+      if (*str == c)
+	res++;
+
+      str++;
+    }
+
+  return res;
+}
+
+/* Parse the tree in ARGS that contains the target attribute information
+   and update the global target options space.  */
+
+static bool
+riscv_process_target_attr (tree args, location_t loc, struct gcc_options *opts)
+{
+  if (TREE_CODE (args) == TREE_LIST)
+    {
+      do
+	{
+	  tree head = TREE_VALUE (args);
+	  if (head)
+	    {
+	      if (!riscv_process_target_attr (head, loc, opts))
+		return false;
+	    }
+	  args = TREE_CHAIN (args);
+      } while (args);
+
+      return true;
+    }
+
+  if (TREE_CODE (args) != STRING_CST)
+    {
+      error_at (loc, "attribute %<target%> argument not a string");
+      return false;
+    }
+  size_t len = strlen (TREE_STRING_POINTER (args));
+  char *str_to_check = (char *) alloca (len + 1);
+  strcpy (str_to_check, TREE_STRING_POINTER (args));
+
+  /* No need to emit warning or error on empty string here, generic code already
+     handle this case.  */
+  if (len == 0)
+    return false;
+
+  /* Used to catch empty spaces between commas i.e.
+     attribute ((target ("attr1;;attr2"))).  */
+  unsigned int num_commas = num_occurences_in_str (';', str_to_check);
+
+  /* Handle multiple target attributes separated by ','.  */
+  char *token = strtok_r (str_to_check, ";", &str_to_check);
+
+  riscv_target_attr_parser attr_parser (loc);
+  unsigned int num_attrs = 0;
+  while (token)
+    {
+      num_attrs++;
+      riscv_process_one_target_attr (token, loc, attr_parser);
+      token = strtok_r (NULL, ";", &str_to_check);
+    }
+
+  if (num_attrs != num_commas + 1)
+    {
+      error_at (loc, "malformed %<target(\"%s\")%> attribute",
+		TREE_STRING_POINTER (args));
+      return false;
+    }
+
+  /* Apply settings from target attribute.  */
+  attr_parser.update_settings (opts);
+
+  return true;
+}
+
+/* Implement TARGET_OPTION_VALID_ATTRIBUTE_P.  This is used to
+   process attribute ((target ("..."))).  */
+
+bool
+riscv_option_valid_attribute_p (tree fndecl, tree, tree args, int)
+{
+  struct cl_target_option cur_target;
+  bool ret;
+  tree new_target;
+  location_t loc = DECL_SOURCE_LOCATION (fndecl);
+
+  /* Save the current target options to restore at the end.  */
+  cl_target_option_save (&cur_target, &global_options, &global_options_set);
+
+  ret = riscv_process_target_attr (args, loc, &global_options);
+
+  if (ret)
+    {
+      riscv_override_options_internal (&global_options);
+      new_target
+	= build_target_option_node (&global_options, &global_options_set);
+    }
+  else
+    new_target = NULL;
+
+  if (fndecl && ret)
+    {
+      DECL_FUNCTION_SPECIFIC_TARGET (fndecl) = new_target;
+    }
+
+  cl_target_option_restore (&global_options, &global_options_set, &cur_target);
+  return ret;
+}
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index c7d0d300345..61c601f78b5 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -73,10 +73,12 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-vectorizer.h"
 #include "gcse.h"
 #include "tree-dfa.h"
+#include "target-globals.h"
 
 /* This file should be included last.  */
 #include "target-def.h"
 #include "riscv-vector-costs.h"
+#include "riscv-subset.h"
 
 /* True if X is an UNSPEC wrapper around a SYMBOL_REF or LABEL_REF.  */
 #define UNSPEC_ADDRESS_P(X)					\
@@ -261,17 +263,6 @@ struct riscv_tune_param
   bool use_divmod_expansion;
 };
 
-/* Information about one micro-arch we know about.  */
-struct riscv_tune_info {
-  /* This micro-arch canonical name.  */
-  const char *name;
-
-  /* Which automaton to use for tuning.  */
-  enum riscv_microarchitecture_type microarchitecture;
-
-  /* Tuning parameters for this micro-arch.  */
-  const struct riscv_tune_param *tune_param;
-};
 
 /* Global variables for machine-dependent things.  */
 
@@ -498,10 +489,23 @@ riscv_min_arithmetic_precision (void)
   return 32;
 }
 
-/* Return the riscv_tune_info entry for the given name string.  */
+template <class T>
+static const char *
+get_tune_str (const T *opts)
+{
+  const char *tune_string = RISCV_TUNE_STRING_DEFAULT;
+  if (opts->x_riscv_tune_string)
+    tune_string = opts->x_riscv_tune_string;
+  else if (opts->x_riscv_cpu_string)
+    tune_string = opts->x_riscv_cpu_string;
+  return tune_string;
+}
+
+/* Return the riscv_tune_info entry for the given name string, return nullptr
+   if NULL_P is true, otherwise return an placeholder and report error.  */
 
-static const struct riscv_tune_info *
-riscv_parse_tune (const char *tune_string)
+const struct riscv_tune_info *
+riscv_parse_tune (const char *tune_string, bool null_p)
 {
   const riscv_cpu_info *cpu = riscv_find_cpu (tune_string);
 
@@ -512,6 +516,9 @@ riscv_parse_tune (const char *tune_string)
     if (strcmp (riscv_tune_info_table[i].name, tune_string) == 0)
       return riscv_tune_info_table + i;
 
+  if (null_p)
+    return nullptr;
+
   error ("unknown cpu %qs for %<-mtune%>", tune_string);
   return riscv_tune_info_table;
 }
@@ -7929,6 +7936,33 @@ riscv_declare_function_name (FILE *stream, const char *name, tree fndecl)
   riscv_asm_output_variant_cc (stream, fndecl, name);
   ASM_OUTPUT_TYPE_DIRECTIVE (stream, name, "function");
   ASM_OUTPUT_LABEL (stream, name);
+  if (DECL_FUNCTION_SPECIFIC_TARGET (fndecl))
+    {
+      fprintf (stream, "\t.option push\n");
+      std::string isa = riscv_current_subset_list ()->to_string (true);
+      fprintf (stream, "\t.option arch, %s\n", isa.c_str ());
+
+      struct cl_target_option *local_cl_target =
+	TREE_TARGET_OPTION (DECL_FUNCTION_SPECIFIC_TARGET (fndecl));
+      struct cl_target_option *global_cl_target =
+	TREE_TARGET_OPTION (target_option_default_node);
+      const char *local_tune_str = get_tune_str (local_cl_target);
+      const char *global_tune_str = get_tune_str (global_cl_target);
+      if (strcmp (local_tune_str, global_tune_str) != 0)
+	fprintf (stream, "\t# tune = %s\n", local_tune_str);
+    }
+}
+
+void
+riscv_declare_function_size (FILE *stream, const char *name, tree fndecl)
+{
+  if (!flag_inhibit_size_directive)
+    ASM_OUTPUT_MEASURED_SIZE (stream, name);
+
+  if (DECL_FUNCTION_SPECIFIC_TARGET (fndecl))
+    {
+      fprintf (stream, "\t.option pop\n");
+    }
 }
 
 /* Implement ASM_OUTPUT_DEF_FROM_DECLS.  */
@@ -8135,16 +8169,18 @@ riscv_override_options_internal (struct gcc_options *opts)
     error ("%<-mdiv%> requires %<-march%> to subsume the %<M%> extension");
 
   /* Likewise floating-point division and square root.  */
-  if ((TARGET_HARD_FLOAT || TARGET_ZFINX) && (target_flags_explicit & MASK_FDIV) == 0)
+  if ((TARGET_HARD_FLOAT_OPTS_P (opts) || TARGET_ZFINX_OPTS_P (opts))
+      && ((target_flags_explicit & MASK_FDIV) == 0))
     opts->x_target_flags |= MASK_FDIV;
 
   /* Handle -mtune, use -mcpu if -mtune is not given, and use default -mtune
      if both -mtune and -mcpu are not given.  */
-  cpu = riscv_parse_tune (opts->x_riscv_tune_string ? opts->x_riscv_tune_string :
-			  (opts->x_riscv_cpu_string ? opts->x_riscv_cpu_string :
-			   RISCV_TUNE_STRING_DEFAULT));
+  const char *tune_string = get_tune_str (opts);
+  cpu = riscv_parse_tune (tune_string, false);
   riscv_microarchitecture = cpu->microarchitecture;
-  tune_param = opts->x_optimize_size ? &optimize_size_tune_info : cpu->tune_param;
+  tune_param = opts->x_optimize_size
+		 ? &optimize_size_tune_info
+		 : cpu->tune_param;
 
   /* Use -mtune's setting for slow_unaligned_access, even when optimizing
      for size.  For architectures that trap and emulate unaligned accesses,
@@ -8156,7 +8192,7 @@ riscv_override_options_internal (struct gcc_options *opts)
   /* Make a note if user explicity passed -mstrict-align for later
      builtin macro generation.  Can't use target_flags_explicitly since
      it is set even for -mno-strict-align.  */
-  riscv_user_wants_strict_align = TARGET_STRICT_ALIGN;
+  riscv_user_wants_strict_align = TARGET_STRICT_ALIGN_OPTS_P (opts);
 
   if ((target_flags_explicit & MASK_STRICT_ALIGN) == 0
       && cpu->tune_param->slow_unaligned_access)
@@ -8314,8 +8350,41 @@ riscv_option_override (void)
   init_machine_status = &riscv_init_machine_status;
 
   riscv_override_options_internal (&global_options);
+
+  /* Save these options as the default ones in case we push and pop them later
+     while processing functions with potential target attributes.  */
+  target_option_default_node = target_option_current_node
+    = build_target_option_node (&global_options, &global_options_set);
+}
+
+/* Restore or save the TREE_TARGET_GLOBALS from or to NEW_TREE.
+   Used by riscv_set_current_function to
+   make sure optab availability predicates are recomputed when necessary.  */
+
+void
+riscv_save_restore_target_globals (tree new_tree)
+{
+  if (TREE_TARGET_GLOBALS (new_tree))
+    restore_target_globals (TREE_TARGET_GLOBALS (new_tree));
+  else if (new_tree == target_option_default_node)
+    restore_target_globals (&default_target_globals);
+  else
+    TREE_TARGET_GLOBALS (new_tree) = save_target_globals_default_opts ();
 }
 
+/* Implements TARGET_OPTION_RESTORE.  Restore the backend codegen decisions
+   using the information saved in PTR.  */
+
+static void
+riscv_option_restore (struct gcc_options *opts,
+		      struct gcc_options * /* opts_set */,
+		      struct cl_target_option * /* ptr */)
+{
+  riscv_override_options_internal (opts);
+}
+
+static GTY (()) tree riscv_previous_fndecl;
+
 /* Implement TARGET_CONDITIONAL_REGISTER_USAGE.  */
 
 static void
@@ -8561,7 +8630,12 @@ riscv_get_interrupt_type (tree decl)
     return MACHINE_MODE;
 }
 
-/* Implement `TARGET_SET_CURRENT_FUNCTION'.  */
+/* Implement `TARGET_SET_CURRENT_FUNCTION'.  Unpack the codegen decisions
+   like tuning and ISA features from the DECL_FUNCTION_SPECIFIC_TARGET
+   of the function, if such exists.  This function may be called multiple
+   times on a single function so use aarch64_previous_fndecl to avoid
+   setting up identical state.  */
+
 /* Sanity cheching for above function attributes.  */
 static void
 riscv_set_current_function (tree decl)
@@ -8569,36 +8643,66 @@ riscv_set_current_function (tree decl)
   if (decl == NULL_TREE
       || current_function_decl == NULL_TREE
       || current_function_decl == error_mark_node
-      || ! cfun->machine
-      || cfun->machine->attributes_checked_p)
+      || ! cfun->machine)
     return;
 
-  cfun->machine->naked_p = riscv_naked_function_p (decl);
-  cfun->machine->interrupt_handler_p
-    = riscv_interrupt_type_p (TREE_TYPE (decl));
+  if (!cfun->machine->attributes_checked_p)
+    {
+      cfun->machine->naked_p = riscv_naked_function_p (decl);
+      cfun->machine->interrupt_handler_p
+	= riscv_interrupt_type_p (TREE_TYPE (decl));
 
-  if (cfun->machine->naked_p && cfun->machine->interrupt_handler_p)
-    error ("function attributes %qs and %qs are mutually exclusive",
-	   "interrupt", "naked");
+      if (cfun->machine->naked_p && cfun->machine->interrupt_handler_p)
+	error ("function attributes %qs and %qs are mutually exclusive",
+	       "interrupt", "naked");
 
-  if (cfun->machine->interrupt_handler_p)
-    {
-      tree ret = TREE_TYPE (TREE_TYPE (decl));
-      tree args = TYPE_ARG_TYPES (TREE_TYPE (decl));
+      if (cfun->machine->interrupt_handler_p)
+	{
+	  tree ret = TREE_TYPE (TREE_TYPE (decl));
+	  tree args = TYPE_ARG_TYPES (TREE_TYPE (decl));
+
+	  if (TREE_CODE (ret) != VOID_TYPE)
+	    error ("%qs function cannot return a value", "interrupt");
 
-      if (TREE_CODE (ret) != VOID_TYPE)
-	error ("%qs function cannot return a value", "interrupt");
+	  if (args && TREE_CODE (TREE_VALUE (args)) != VOID_TYPE)
+	    error ("%qs function cannot have arguments", "interrupt");
 
-      if (args && TREE_CODE (TREE_VALUE (args)) != VOID_TYPE)
-	error ("%qs function cannot have arguments", "interrupt");
+	  cfun->machine->interrupt_mode = riscv_get_interrupt_type (decl);
 
-      cfun->machine->interrupt_mode = riscv_get_interrupt_type (decl);
+	  gcc_assert (cfun->machine->interrupt_mode != UNKNOWN_MODE);
+	}
 
-      gcc_assert (cfun->machine->interrupt_mode != UNKNOWN_MODE);
+      /* Don't print the above diagnostics more than once.  */
+      cfun->machine->attributes_checked_p = 1;
     }
 
-  /* Don't print the above diagnostics more than once.  */
-  cfun->machine->attributes_checked_p = 1;
+  if (!decl || decl == riscv_previous_fndecl)
+    return;
+
+  tree old_tree = (riscv_previous_fndecl
+		     ? DECL_FUNCTION_SPECIFIC_TARGET (riscv_previous_fndecl)
+		     : NULL_TREE);
+
+  tree new_tree = DECL_FUNCTION_SPECIFIC_TARGET (decl);
+
+  /* If current function has no attributes but the previous one did,
+     use the default node.  */
+  if (!new_tree && old_tree)
+    new_tree = target_option_default_node;
+
+  /* If nothing to do, return.  #pragma GCC reset or #pragma GCC pop to
+     the default have been handled by aarch64_save_restore_target_globals from
+     aarch64_pragma_target_parse.  */
+  if (old_tree == new_tree)
+    return;
+
+  riscv_previous_fndecl = decl;
+
+  /* First set the target options.  */
+  cl_target_option_restore (&global_options, &global_options_set,
+			    TREE_TARGET_OPTION (new_tree));
+
+  riscv_save_restore_target_globals (new_tree);
 }
 
 /* Implement TARGET_MERGE_DECL_ATTRIBUTES. */
@@ -9706,6 +9810,12 @@ riscv_preferred_else_value (unsigned ifn, tree vectype, unsigned int nops,
 #undef TARGET_OPTION_OVERRIDE
 #define TARGET_OPTION_OVERRIDE riscv_option_override
 
+#undef TARGET_OPTION_RESTORE
+#define TARGET_OPTION_RESTORE riscv_option_restore
+
+#undef TARGET_OPTION_VALID_ATTRIBUTE_P
+#define TARGET_OPTION_VALID_ATTRIBUTE_P riscv_option_valid_attribute_p
+
 #undef TARGET_LEGITIMIZE_ADDRESS
 #define TARGET_LEGITIMIZE_ADDRESS riscv_legitimize_address
 
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index 7ac78847b3a..a88c6f917a6 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -25,6 +25,8 @@ along with GCC; see the file COPYING3.  If not see
 #include <stdbool.h>
 #include "config/riscv/riscv-opts.h"
 
+#define SWITCHABLE_TARGET 1
+
 /* Target CPU builtins.  */
 #define TARGET_CPU_CPP_BUILTINS() riscv_cpu_cpp_builtins (pfile)
 
@@ -1054,6 +1056,10 @@ while (0)
 #define ASM_DECLARE_FUNCTION_NAME(STR, NAME, DECL)                             \
   riscv_declare_function_name (STR, NAME, DECL)
 
+#undef ASM_DECLARE_FUNCTION_SIZE
+#define ASM_DECLARE_FUNCTION_SIZE(FILE, FNAME, DECL)                           \
+  riscv_declare_function_size (FILE, FNAME, DECL)
+
 /* Add output .variant_cc directive for specific alias definition.  */
 #undef ASM_OUTPUT_DEF_FROM_DECLS
 #define ASM_OUTPUT_DEF_FROM_DECLS(STR, DECL, TARGET)                           \
diff --git a/gcc/config/riscv/riscv.opt b/gcc/config/riscv/riscv.opt
index e35ce2de364..4898e3b8260 100644
--- a/gcc/config/riscv/riscv.opt
+++ b/gcc/config/riscv/riscv.opt
@@ -84,11 +84,11 @@ Target RejectNegative Joined Negative(march=)
 lower-case.
 
 mtune=
-Target RejectNegative Joined Var(riscv_tune_string)
+Target RejectNegative Joined Var(riscv_tune_string) Save
 -mtune=PROCESSOR	Optimize the output for PROCESSOR.
 
 mcpu=
-Target RejectNegative Joined Var(riscv_cpu_string)
+Target RejectNegative Joined Var(riscv_cpu_string) Save
 -mcpu=PROCESSOR	Use architecture of and optimize the output for PROCESSOR.
 
 msmall-data-limit=
@@ -106,7 +106,7 @@ memory accesses to be generated as compressed instructions.  Currently targets
 32-bit integer load/stores.
 
 mcmodel=
-Target RejectNegative Joined Enum(code_model) Var(riscv_cmodel) Init(TARGET_DEFAULT_CMODEL)
+Target RejectNegative Joined Enum(code_model) Var(riscv_cmodel) Init(TARGET_DEFAULT_CMODEL) Save
 Specify the code model.
 
 mstrict-align
diff --git a/gcc/config/riscv/t-riscv b/gcc/config/riscv/t-riscv
index f137e1f17ef..d46459ff462 100644
--- a/gcc/config/riscv/t-riscv
+++ b/gcc/config/riscv/t-riscv
@@ -108,6 +108,11 @@ riscv-v.o: $(srcdir)/config/riscv/riscv-v.cc \
 	$(COMPILE) $<
 	$(POSTCOMPILE)
 
+riscv-target-attr.o: $(srcdir)/config/riscv/riscv-target-attr.cc $(CONFIG_H) \
+  $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) $(DIAGNOSTIC_CORE_H)
+	$(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) \
+		$(srcdir)/config/riscv/riscv-target-attr.cc
+
 thead.o: $(srcdir)/config/riscv/thead.cc \
   $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TARGET_H) backend.h $(RTL_H) \
   memmodel.h $(EMIT_RTL_H) poly-int.h output.h
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index b82497f00e4..d39d599ccb5 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -6227,8 +6227,66 @@ void f (void) __attribute__ ((interrupt ("user")));
 Permissible values for this parameter are @code{user}, @code{supervisor},
 and @code{machine}.  If there is no parameter, then it defaults to
 @code{machine}.
+
+@end table
+
+The following target-specific function attributes are available for the
+RISC-V target.  For the most part, these options mirror the behavior of
+similar command-line options (@pxref{RISC-V Options}), but on a
+per-function basis.
+
+@table @code
+@cindex @code{arch=} function attribute, RISC-V
+@item arch=
+Specifies the architecture version and architectural extensions to use
+for this function.  The behavior and permissible arguments are the same as
+for the @option{-march=} command-line option, in addtion, it also support
+extension enablement list, a list of extension name and prefixed with @code{+},
+like @code{arch=+zba} means enable @code{zba} extension.
+Multiple extension can be enabled by separating them with a comma.  For example:
+@code{arch=+zba,+zbb}.
+
+@cindex @code{tune=} function attribute, RISC-V
+@item tune=
+Specifies the core for which to tune the performance of this function.
+The behavior and permissible arguments are the same as for the @option{-mtune=}
+command-line option.
+
+@cindex @code{cpu=} function attribute, RISC-V
+@item cpu=
+Specifies the core for which to tune the performance of this function and also
+whose architectural features to use.  The behavior and valid arguments are the
+same as for the @option{-mcpu=} command-line option.
+
 @end table
 
+The above target attributes can be specified as follows:
+
+@smallexample
+__attribute__((target("@var{attr-string}")))
+int
+f (int a)
+@{
+  return a + 5;
+@}
+@end smallexample
+
+where @code{@var{attr-string}} is one of the attribute strings specified above.
+
+Multiple target function attributes can be specified by separating them with
+a semicolon.  For example:
+@smallexample
+__attribute__((target("arch=+zba,+zbb;tune=rocket")))
+int
+foo (int a)
+@{
+  return a + 5;
+@}
+@end smallexample
+
+is valid and compiles function @code{foo} with @code{zba}
+and @code{zbb} extensions and tunes it for @code{rocket}.
+
 @node RL78 Function Attributes
 @subsection RL78 Function Attributes
 
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-01.c b/gcc/testsuite/gcc.target/riscv/target-attr-01.c
new file mode 100644
index 00000000000..b3f3d65d543
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-01.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+
+/*
+** foo:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+
+
+long foo() __attribute__((target("arch=rv64gc_zba")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   slli\s*a1,a1,1
+**   add\s*a0,a1,a0
+**   ...
+*/
+
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-02.c b/gcc/testsuite/gcc.target/riscv/target-attr-02.c
new file mode 100644
index 00000000000..c010089a823
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-02.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+
+/*
+** foo:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+
+
+long foo() __attribute__((target("arch=+zba")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   slli\s*a1,a1,1
+**   add\s*a0,a1,a0
+**   ...
+*/
+
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-03.c b/gcc/testsuite/gcc.target/riscv/target-attr-03.c
new file mode 100644
index 00000000000..b4896cb2e27
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-03.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc_zba -O2 -mabi=lp64" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/*
+** foo:
+**   ...
+**   slli\s*a1,a1,1
+**   add\s*a0,a1,a0
+**   ...
+*/
+long foo() __attribute__((target("arch=rv64gc")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-04.c b/gcc/testsuite/gcc.target/riscv/target-attr-04.c
new file mode 100644
index 00000000000..369d6514e5a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-04.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc_zba -O2 -mabi=lp64 -mtune=rocket" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/*
+** foo:
+**   ...
+**   # tune = sifive-7-series
+**   ...
+**   slli\s*a1,a1,1
+**   add\s*a0,a1,a0
+**   ...
+*/
+long foo() __attribute__((target("cpu=sifive-u74")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-05.c b/gcc/testsuite/gcc.target/riscv/target-attr-05.c
new file mode 100644
index 00000000000..c75368dcebf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-05.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc_zba -O2 -mabi=lp64 -mtune=rocket" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/*
+** foo:
+**   ...
+**   # tune = sifive-7-series
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long foo() __attribute__((target("cpu=sifive-u74;arch=rv64gc_zba")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-06.c b/gcc/testsuite/gcc.target/riscv/target-attr-06.c
new file mode 100644
index 00000000000..369c95eeb54
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-06.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc_zba -O2 -mabi=lp64 -mtune=rocket" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/*
+** foo:
+**   ...
+**   # tune = sifive-5-series
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long foo() __attribute__((target("cpu=sifive-u74;tune=sifive-5-series;arch=rv64gc_zba")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-07.c b/gcc/testsuite/gcc.target/riscv/target-attr-07.c
new file mode 100644
index 00000000000..4ff81166a62
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-07.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc_zba -O2 -mabi=lp64 -mtune=rocket" } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+/*
+** foo:
+**   ...
+**   # tune = sifive-5-series
+**   ...
+*/
+long foo() __attribute__((target("tune=sifive-5-series")));
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+/*
+** bar:
+**   ...
+**   sh1add\s*a0,a1,a0
+**   ...
+*/
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-01.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-01.c
new file mode 100644
index 00000000000..91cbcaac21d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-01.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target("arch=rv64gc_zba;;"))); /* { dg-error "malformed" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-02.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-02.c
new file mode 100644
index 00000000000..0c838bb3ca7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-02.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target("cpu=xyz-cpu"))); /* { dg-error "unknown CPU" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-03.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-03.c
new file mode 100644
index 00000000000..09702d1690a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-03.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target("tune=xyz-cpu"))); /* { dg-error "unknown TUNE" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-04.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-04.c
new file mode 100644
index 00000000000..1d9a0ffdd88
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-04.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target(123))); /* { dg-error "argument not a string" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-05.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-05.c
new file mode 100644
index 00000000000..24a81c5279b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-05.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target(""))); /* { dg-warning "empty string in attribute .target." } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-06.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-06.c
new file mode 100644
index 00000000000..a0d65859d40
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-06.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target("arch=*x"))); /* { dg-error "must start with \\+ or rv" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-bad-07.c b/gcc/testsuite/gcc.target/riscv/target-attr-bad-07.c
new file mode 100644
index 00000000000..8aa82504dc1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-bad-07.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+
+long foo() __attribute__((target("arch=+zbb_zba"))); /* { dg-error "extension 'zbb_zba' starts with 'z' but is unsupported standard extension" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
+
+long bar(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-warning-01.c b/gcc/testsuite/gcc.target/riscv/target-attr-warning-01.c
new file mode 100644
index 00000000000..d4b6e4e505a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-warning-01.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+long foo() __attribute__((target("arch=rv64gc_zba;arch=rv64gc_zba"))); /* { dg-warning "arch appears more than once" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-warning-02.c b/gcc/testsuite/gcc.target/riscv/target-attr-warning-02.c
new file mode 100644
index 00000000000..b43b131c955
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-warning-02.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+long foo() __attribute__((target("cpu=sifive-u74;cpu=sifive-u74"))); /* { dg-warning "cpu appears more than once" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
diff --git a/gcc/testsuite/gcc.target/riscv/target-attr-warning-03.c b/gcc/testsuite/gcc.target/riscv/target-attr-warning-03.c
new file mode 100644
index 00000000000..551c56e37dc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/target-attr-warning-03.c
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } } */
+/* { dg-options "-march=rv64gc -O2 -mabi=lp64" } */
+
+long foo() __attribute__((target("tune=sifive-u74;tune=sifive-u74"))); /* { dg-warning "tune appears more than once" } */
+long foo(long a, long b){
+  return a + (b * 2);
+}
