[PATCH 2/3] RISC-V: Support -march=native on Linux hosts

Kito Cheng <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.