Detect the ISA with the Linux hwprobe syscall and rewrite -march=native
into an ordinary -march= option, letting riscv_expand_arch canonicalise
the result.

gcc/ChangeLog:

        * config.host: Build driver-riscv.o for riscv*-*-linux* hosts.
        * config/riscv/driver-riscv.cc: New file.
        * config/riscv/x-riscv: New file.
        * config/riscv/riscv.h (host_detect_local_cpu): Declare.
        (HAVE_LOCAL_CPU_DETECT): Define.
        (RISCV_NATIVE_SPEC_FUNCTIONS): Define.
        (RISCV_NATIVE_SPECS): Define.
        (EXTRA_SPEC_FUNCTIONS): Add RISCV_NATIVE_SPEC_FUNCTIONS.
        (DRIVER_SELF_SPECS): Add RISCV_NATIVE_SPECS.
        * doc/invoke.texi (RISC-V Options): Document -march=native.

gcc/testsuite/ChangeLog:

        * lib/target-supports.exp
        (check_effective_target_riscv_native_cpu_detect): New.
        * gcc.target/riscv/march-native-1.c: New test.
---
 gcc/common/config/riscv/riscv-hwprobe.h       |   3 +
 gcc/config.host                               |   8 +
 gcc/config/riscv/driver-riscv.cc              | 162 ++++++++++++++++++
 gcc/config/riscv/riscv.h                      |  19 +-
 gcc/config/riscv/x-riscv                      |   5 +
 gcc/doc/invoke.texi                           |  10 ++
 .../gcc.target/riscv/march-native-1.c         |  21 +++
 gcc/testsuite/lib/target-supports.exp         |  14 ++
 8 files changed, 241 insertions(+), 1 deletion(-)
 create mode 100644 gcc/config/riscv/driver-riscv.cc
 create mode 100644 gcc/config/riscv/x-riscv
 create mode 100644 gcc/testsuite/gcc.target/riscv/march-native-1.c

diff --git a/gcc/common/config/riscv/riscv-hwprobe.h 
b/gcc/common/config/riscv/riscv-hwprobe.h
index f1fe2aeac2c..ba989b99d2f 100644
--- a/gcc/common/config/riscv/riscv-hwprobe.h
+++ b/gcc/common/config/riscv/riscv-hwprobe.h
@@ -33,6 +33,9 @@ see the files COPYING3 and COPYING.RUNTIME respectively.  If 
not, see
 enum {
 #define RISCV_HWPROBE_KEY(NAME, VALUE) RISCV_HWPROBE_KEY_##NAME = VALUE,
 #include "common/config/riscv/riscv-hwprobe.def"
+#define RISCV_HWPROBE_EXT(NAME, UPPERCASE_NAME, KEY, BIT, XLEN) \
+  RISCV_HWPROBE_EXT_##UPPERCASE_NAME = (1ULL << BIT),
+#include "common/config/riscv/riscv-hwprobe.def"
 };
 
 #define RISCV_HWPROBE_BASE_BEHAVIOR_IMA (1ULL << 0)
diff --git a/gcc/config.host b/gcc/config.host
index c9be68acb82..7e77e422d5b 100644
--- a/gcc/config.host
+++ b/gcc/config.host
@@ -142,6 +142,14 @@ case ${host} in
       ;;
     esac
     ;;
+  riscv*-*-linux*)
+    case ${target} in
+      riscv*-*-*)
+       host_extra_gcc_objs="driver-riscv.o"
+       host_xmake_file="${host_xmake_file} riscv/x-riscv"
+       ;;
+    esac
+    ;;
   rs6000-*-* \
   | powerpc*-*-* )
     case ${target} in
diff --git a/gcc/config/riscv/driver-riscv.cc b/gcc/config/riscv/driver-riscv.cc
new file mode 100644
index 00000000000..2997c5c2fe9
--- /dev/null
+++ b/gcc/config/riscv/driver-riscv.cc
@@ -0,0 +1,162 @@
+/* Native CPU detection for RISC-V.
+   Copyright (C) 2026 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
+
+#include "config.h"
+#define INCLUDE_STRING
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+
+#ifdef __linux__
+#include "common/config/riscv/riscv-hwprobe.h"
+
+/* One entry per extension the kernel can report.  */
+
+struct riscv_hwprobe_ext
+{
+  const char *name;
+  int key;
+  int bit;
+  int xlen;
+};
+
+static const struct riscv_hwprobe_ext riscv_hwprobe_exts[] = {
+#define RISCV_HWPROBE_EXT(NAME, UPPERCASE_NAME, KEY, BIT, XLEN)                
\
+  { NAME, KEY, BIT, XLEN },
+#include "common/config/riscv/riscv-hwprobe.def"
+};
+
+/* Append the Zvl extension for this machine's vector register width,
+   which only the vlenb CSR reports.  */
+
+static void
+riscv_add_vlen (std::string &isa)
+{
+  unsigned long vlenb;
+  unsigned long vlen;
+  char buf[32];
+
+  __asm__ volatile ("csrr %0, 0xc22" : "=r" (vlenb));
+
+  vlen = vlenb * 8;
+
+  gcc_assert (vlen >= 32 && vlen <= 65536 && (vlen & (vlen - 1)) == 0);
+
+  snprintf (buf, sizeof (buf), "_zvl%lub", vlen);
+  isa += buf;
+}
+
+/* Build the ISA string from what hwprobe reports, and set *VECTOR_P if the
+   machine can execute vector instructions.  Return false if hwprobe cannot
+   describe the machine.  */
+
+static bool
+riscv_arch_from_hwprobe (std::string &isa, bool *vector_p)
+{
+  struct riscv_hwprobe pairs[] = {
+    { RISCV_HWPROBE_KEY_IMA_EXT_0, 0 },
+    { RISCV_HWPROBE_KEY_IMA_EXT_1, 0 }
+  };
+  size_t npairs = ARRAY_SIZE (pairs);
+  unsigned long long ima0;
+
+  if (riscv_hwprobe (pairs, npairs) != 0)
+    return false;
+
+  const int xlen = __riscv_xlen;
+
+  isa = xlen == 32 ? "rv32ima" : "rv64ima";
+
+  for (size_t i = 0; i < ARRAY_SIZE (riscv_hwprobe_exts); i++)
+    {
+      const struct riscv_hwprobe_ext *ext = &riscv_hwprobe_exts[i];
+      unsigned long long value;
+
+      if (ext->xlen != 0 && ext->xlen != xlen)
+       continue;
+
+      value = riscv_hwprobe_value (pairs, npairs, ext->key);
+      if (value & (1ULL << ext->bit))
+       {
+         isa += '_';
+         isa += ext->name;
+       }
+    }
+
+  ima0 = riscv_hwprobe_value (pairs, npairs, RISCV_HWPROBE_KEY_IMA_EXT_0);
+
+  if (ima0 & (RISCV_HWPROBE_IMA_V
+             | RISCV_HWPROBE_EXT_ZVE32X
+             | RISCV_HWPROBE_EXT_ZVE64X))
+    *vector_p = true;
+
+  return true;
+}
+
+/* Return the ISA string describing this machine, or NULL if it cannot be
+   determined.  */
+
+static const char *
+riscv_native_arch (void)
+{
+  std::string isa;
+  bool vector_p = false;
+
+  if (!riscv_arch_from_hwprobe (isa, &vector_p))
+    return NULL;
+
+  if (vector_p)
+    riscv_add_vlen (isa);
+
+  return xstrdup (isa.c_str ());
+}
+
+#else /* !__linux__ */
+
+static const char *
+riscv_native_arch (void)
+{
+  return NULL;
+}
+
+#endif /* __linux__ */
+
+/* Implement the local_cpu_detect spec function.  ARGV[0] selects what to
+   report: "arch" for an -march= option.  */
+
+const char *
+host_detect_local_cpu (int argc, const char **argv)
+{
+  if (argc < 1 || argv[0] == NULL)
+    return NULL;
+
+  if (strcmp (argv[0], "arch") == 0)
+    {
+      const char *isa = riscv_native_arch ();
+
+      if (isa == NULL)
+       return NULL;
+
+      return concat ("-march=", isa, NULL);
+    }
+
+  return NULL;
+}
diff --git a/gcc/config/riscv/riscv.h b/gcc/config/riscv/riscv.h
index 4ba6ad260e5..80356eacadb 100644
--- a/gcc/config/riscv/riscv.h
+++ b/gcc/config/riscv/riscv.h
@@ -52,12 +52,26 @@ extern const char *riscv_default_mtune (int argc, const 
char **argv);
 extern const char *riscv_multi_lib_check (int argc, const char **argv);
 extern const char *riscv_arch_help (int argc, const char **argv);
 
+#if defined (__riscv) && defined (__linux__)
+extern const char *host_detect_local_cpu (int argc, const char **argv);
+#define HAVE_LOCAL_CPU_DETECT
+# define RISCV_NATIVE_SPEC_FUNCTIONS                                   \
+  { "local_cpu_detect", host_detect_local_cpu },
+
+# define RISCV_NATIVE_SPECS                                            \
+  "%{march=native:%<march=native %:local_cpu_detect(arch)} "
+#else
+# define RISCV_NATIVE_SPEC_FUNCTIONS
+# define RISCV_NATIVE_SPECS ""
+#endif
+
 # define EXTRA_SPEC_FUNCTIONS                                          \
   { "riscv_expand_arch", riscv_expand_arch },                          \
   { "riscv_expand_arch_from_cpu", riscv_expand_arch_from_cpu },                
\
   { "riscv_default_mtune", riscv_default_mtune },                      \
   { "riscv_multi_lib_check", riscv_multi_lib_check },                  \
-  { "riscv_arch_help", riscv_arch_help },
+  { "riscv_arch_help", riscv_arch_help },                              \
+  RISCV_NATIVE_SPEC_FUNCTIONS
 
 /* Support for a compile-time default CPU, et cetera.  The rules are:
    --with-cpu is ignored if -mcpu is specified.
@@ -119,8 +133,11 @@ ASM_MISA_SPEC
 #define ARCH_UNSET_CLEANUP_SPECS  \
   "%{march=unset:%<march=*} "  \
 
+/* RISCV_NATIVE_SPECS comes first so that the specs below, which expand and
+   validate -march=, see the option it produced.  */
 #undef DRIVER_SELF_SPECS
 #define DRIVER_SELF_SPECS                                      \
+RISCV_NATIVE_SPECS,                                            \
 ARCH_UNSET_CLEANUP_SPECS \
 "%{march=help:%:riscv_arch_help()} "                           \
 "%{print-supported-extensions:%:riscv_arch_help()} "           \
diff --git a/gcc/config/riscv/x-riscv b/gcc/config/riscv/x-riscv
new file mode 100644
index 00000000000..55f32d7dd1c
--- /dev/null
+++ b/gcc/config/riscv/x-riscv
@@ -0,0 +1,5 @@
+driver-riscv.o: $(srcdir)/config/riscv/driver-riscv.cc \
+  $(CONFIG_H) $(SYSTEM_H) $(TM_H) $(CORETYPES_H) \
+  $(srcdir)/common/config/riscv/riscv-hwprobe.h \
+  $(srcdir)/common/config/riscv/riscv-hwprobe.def
+       $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) $<
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 2e53a8c8a7e..5cc8dcca3c8 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -31325,6 +31325,16 @@ passed.  This is useful for ensuring that the 
architecture is taken from the
 @option{-mcpu} option, and an error results if no @option{-mcpu} option
 is given when @option{-march=unset} is used.
 
+@option{-march=native} causes the compiler to work out the ISA of the
+machine it is running on.  It is only available on a native GNU/Linux
+compiler; anywhere else the value is rejected like any other unknown ISA
+string.  On a system whose cores are not all alike, such as one pairing
+fast and efficient cores, which of them the detected ISA describes is
+unspecified; write the ISA string out in full if it matters.  If the
+running machine cannot be identified the option is ignored, and the
+architecture is taken from @option{-mcpu} or from the configured default
+as usual.
+
 The syntax of the ISA string is defined as follows:
 
 @itemize @bullet{}
diff --git a/gcc/testsuite/gcc.target/riscv/march-native-1.c 
b/gcc/testsuite/gcc.target/riscv/march-native-1.c
new file mode 100644
index 00000000000..6ffd686e515
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/march-native-1.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target riscv_native_cpu_detect } */
+/* { dg-options "-march=native" } */
+
+#ifndef __riscv_arch_test
+#error "-march=native did not produce a usable ISA string"
+#endif
+
+#ifndef __riscv_mul
+#error "-march=native lost the M extension"
+#endif
+
+#ifndef __riscv_atomic
+#error "-march=native lost the A extension"
+#endif
+
+int
+main (void)
+{
+  return 0;
+}
diff --git a/gcc/testsuite/lib/target-supports.exp 
b/gcc/testsuite/lib/target-supports.exp
index eb2444204a0..fc5309dad41 100644
--- a/gcc/testsuite/lib/target-supports.exp
+++ b/gcc/testsuite/lib/target-supports.exp
@@ -2064,6 +2064,20 @@ proc check_effective_target_rv_float_abi_soft { } {
     }]
 }
 
+# Return 1 if the compiler under test can work out the ISA of the machine
+# it is running on, 0 otherwise.  This is only true of a native RISC-V
+# Linux compiler; a cross compiler rejects -march=native outright.
+# Cache the result.
+
+proc check_effective_target_riscv_native_cpu_detect { } {
+    if { ![istarget riscv*-*-linux*] } {
+       return 0
+    }
+    return [check_no_compiler_messages riscv_native_cpu_detect object {
+       int dummy;
+    } "-march=native"]
+}
+
 # Return 1 if the target arch supports the atomic extension, 0 otherwise.
 # Cache the result.
 
-- 
2.53.0

Reply via email to