[RFC,06/19] riscv: Adding ISA string parser for environment variables

Message ID 20230207001618.458947-7-christoph.muellner@vrull.eu
State New
Headers
Series riscv: ifunc support with optimized mem*/str*/cpu_relax routines |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Christoph Müllner Feb. 7, 2023, 12:16 a.m. UTC
  From: Christoph Müllner <christoph.muellner@vrull.eu>

RISC-V does not have a reliable mechanism to detect hart features
like supported ISA extensions or cache block sizes at run-time
as of now.

Not knowing the hart features limits optimization strategies of glibc
(e.g. ifunc support requires run-time hard feature knowledge).

To circumvent this limitation this patch introduces a mechanism to
get the hart features via environment variables:
* RISCV_RT_MARCH represents a lower-case ISA string (-march string)
  E.g. RISCV_RT_MARCH=rv64gc_zicboz
* RISCV_RT_CBOM_BLOCKSIZE represents the cbom instruction block size
  E.g. RISCV_RT_CBOZ_BLOCKSIZE=64
* RISCV_RT_CBOZ_BLOCKSIZE represents the cboz instruction block size

These environment variables are parsed during startup and the found
ISA extensions are stored a struct (hart_features) for evaluation
by dynamic dispatching code.

As the parser code is executed very early, we cannot call functions
that have direct or indirect (via getenv()) dependencies to strlen()
and strncmp(), as these functions cannot be called before the ifunc
support is initialized. Therefore, this patch contains its own helper
functions for strlen(), strncmp(), and getenv().

Signed-off-by: Christoph Müllner <christoph.muellner@vrull.eu>
---
 sysdeps/unix/sysv/linux/riscv/hart-features.c | 294 ++++++++++++++++++
 .../unix/sysv/linux/riscv/macro-for-each.h    |  24 ++
 2 files changed, 318 insertions(+)
 create mode 100644 sysdeps/unix/sysv/linux/riscv/macro-for-each.h
  

Comments

David Abdurachmanov Feb. 7, 2023, 6:20 a.m. UTC | #1
On Tue, Feb 7, 2023 at 1:20 AM Christoph Muellner
<christoph.muellner@vrull.eu> wrote:
>
> From: Christoph Müllner <christoph.muellner@vrull.eu>
>
> RISC-V does not have a reliable mechanism to detect hart features
> like supported ISA extensions or cache block sizes at run-time
> as of now.
>
> Not knowing the hart features limits optimization strategies of glibc
> (e.g. ifunc support requires run-time hard feature knowledge).
>
> To circumvent this limitation this patch introduces a mechanism to
> get the hart features via environment variables:
> * RISCV_RT_MARCH represents a lower-case ISA string (-march string)
>   E.g. RISCV_RT_MARCH=rv64gc_zicboz
> * RISCV_RT_CBOM_BLOCKSIZE represents the cbom instruction block size
>   E.g. RISCV_RT_CBOZ_BLOCKSIZE=64
> * RISCV_RT_CBOZ_BLOCKSIZE represents the cboz instruction block size

Hi,

Non-expert here, but have you considered defining RISCV glibc
tunables? It's designed for this purpose.

See: https://www.gnu.org/software/libc/manual/html_node/Tunables.html
Especially hardware capability tunables:
https://www.gnu.org/software/libc/manual/html_node/Hardware-Capability-Tunables.html

It would be nice to have tunables defined for RISC-V that could stay
with us for a long time.

Cheers,
david

> These environment variables are parsed during startup and the found
> ISA extensions are stored a struct (hart_features) for evaluation
> by dynamic dispatching code.
>
> As the parser code is executed very early, we cannot call functions
> that have direct or indirect (via getenv()) dependencies to strlen()
> and strncmp(), as these functions cannot be called before the ifunc
> support is initialized. Therefore, this patch contains its own helper
> functions for strlen(), strncmp(), and getenv().
>
> Signed-off-by: Christoph Müllner <christoph.muellner@vrull.eu>
> ---
>  sysdeps/unix/sysv/linux/riscv/hart-features.c | 294 ++++++++++++++++++
>  .../unix/sysv/linux/riscv/macro-for-each.h    |  24 ++
>  2 files changed, 318 insertions(+)
>  create mode 100644 sysdeps/unix/sysv/linux/riscv/macro-for-each.h
>
> diff --git a/sysdeps/unix/sysv/linux/riscv/hart-features.c b/sysdeps/unix/sysv/linux/riscv/hart-features.c
> index 41111eff57..6de41a26cc 100644
> --- a/sysdeps/unix/sysv/linux/riscv/hart-features.c
> +++ b/sysdeps/unix/sysv/linux/riscv/hart-features.c
> @@ -17,12 +17,17 @@
>     <https://www.gnu.org/licenses/>.  */
>
>  #include <hart-features.h>
> +#include <macro-for-each.h>
> +#include <string_private.h>
>
>  /* The code in this file is executed very early, so we cannot call
>     indirect functions because ifunc support is not initialized.
>     Therefore this file adds a few simple helper functions to avoid
>     dependencies to functions outside of this file.  */
>
> +#define xstr(s) str(s)
> +#define str(s) #s
> +
>  static inline void
>  inhibit_loop_to_libcall
>  simple_memset (void *s, int c, size_t n)
> @@ -35,9 +40,298 @@ simple_memset (void *s, int c, size_t n)
>      }
>  }
>
> +static inline size_t
> +inhibit_loop_to_libcall
> +simple_strlen (const char *s)
> +{
> +  size_t n = 0;
> +  char c = *s;
> +  while (c != 0)
> +    {
> +      s++;
> +      n++;
> +      c = *s;
> +    }
> +  return n;
> +}
> +
> +static inline int
> +inhibit_loop_to_libcall
> +simple_strncmp (const char *s1, const char *s2, size_t n)
> +{
> +  while (n != 0)
> +    {
> +      if (*s1 == 0 || *s1 != *s2)
> +       return *((const unsigned char *)s1) - *((const unsigned char *)s2);
> +      n--;
> +      s1++;
> +      s2++;
> +    }
> +  return 0;
> +}
> +
> +extern char **__environ;
> +static inline char*
> +simple_getenv (const char *name)
> +{
> +  char **ep;
> +  uint16_t name_start;
> +
> +  if (__environ == NULL || name[0] == 0 || name[1] == 0)
> +    return NULL;
> +
> +  size_t len = simple_strlen (name);
> +#if _STRING_ARCH_unaligned
> +  name_start = *(const uint16_t *) name;
> +#else
> +  name_start = (((const unsigned char *) name)[0]
> +               | (((const unsigned char *) name)[1] << 8));
> +#endif
> +  len -= 2;
> +  name += 2;
> +
> +  for (ep = __environ; *ep != NULL; ++ep)
> +    {
> +#if _STRING_ARCH_unaligned
> +      uint16_t ep_start = *(uint16_t *) *ep;
> +#else
> +      uint16_t ep_start = (((unsigned char *) *ep)[0]
> +                          | (((unsigned char *) *ep)[1] << 8));
> +#endif
> +      if (name_start == ep_start && !simple_strncmp (*ep + 2, name, len)
> +         && (*ep)[len + 2] == '=')
> +       return &(*ep)[len + 3];
> +    }
> +  return NULL;
> +}
> +
> +/* Check if the given number is a power of 2.
> +   Return true if so, or false otherwise.  */
> +static inline int
> +is_power_of_two (unsigned long v)
> +{
> +  return (v & (v - 1)) == 0;
> +}
> +
> +/* Check if the given string str starts with
> +   the prefix pre.  Return true if so, or false
> +   otherwise.  */
> +static inline int
> +starts_with (const char *str, const char *pre)
> +{
> +  return simple_strncmp (pre, str, simple_strlen (pre)) == 0;
> +}
> +
> +/* Lower all characters of a string up to the
> +   first NUL-character in the string.  */
> +static inline void
> +strtolower (char *s)
> +{
> +  char c = *s;
> +  while (c != '\0')
> +    {
> +      if (c >= 'A' && c <= 'Z')
> +       *s = c + 'a' - 'A';
> +      s++;
> +      c = *s;
> +    }
> +}
> +
> +/* Count the number of detected extensions.  */
> +static inline unsigned long
> +count_extensions (struct hart_features *hart_features)
> +{
> +  unsigned long n = 0;
> +#define ISA_EXT(e)                                                     \
> +  if (hart_features->have_##e == 1)                                    \
> +    n++;
> +#define ISA_EXT_GROUP(g, ...)                                          \
> +  if (hart_features->have_##g == 1)                                    \
> +    n++;
> +#include "isa-extensions.def"
> +  return n;
> +}
> +
> +/* Check if the given charater is not '0'-'9'.  */
> +static inline int
> +notanumber (const char c)
> +{
> +  return (c < '0' || c > '9');
> +}
> +
> +/* Parse RISCV_RT_MARCH and store found extensions.  */
> +static inline void
> +parse_rt_march (struct hart_features *hart_features)
> +{
> +  const char* s = simple_getenv ("RISCV_RT_MARCH");
> +  if (s == NULL)
> +    goto end;
> +
> +  hart_features->rt_march = s;
> +
> +  /* "RISC-V ISA strings begin with either RV32I, RV32E, RV64I, or RV128I
> +      indicating the supported address space size in bits for the base
> +      integer ISA."  */
> +  if (starts_with (s, "rv32") && notanumber (*(s+4)))
> +    {
> +      hart_features->xlen = 32;
> +      s += 4;
> +    }
> +  else if (starts_with (s, "rv64") && notanumber (*(s+4)))
> +    {
> +      hart_features->xlen = 64;
> +      s += 4;
> +    }
> +  else if (starts_with (s, "rv128") && notanumber (*(s+5)))
> +    {
> +      hart_features->xlen = 128;
> +      s += 5;
> +    }
> +  else
> +    {
> +      goto fail;
> +    }
> +
> +  /* Parse the extensions.  */
> +  const char *s_old = s;
> +  while (*s != '\0')
> +    {
> +#define ISA_EXT(e)                                                     \
> +      else if (starts_with (s, xstr (e)))                              \
> +       {                                                               \
> +         hart_features->have_##e = 1;                                  \
> +         s += simple_strlen (xstr (e));                                \
> +       }
> +#define ISA_EXT_GROUP(g, ...)                                          \
> +      ISA_EXT (g)
> +      if (0);
> +#include "isa-extensions.def"
> +
> +      /* Consume optional version information.  */
> +      while (*s >= '0' && *s <= '9')
> +       s++;
> +      while (*s == 'p')
> +       s++;
> +      while (*s >= '0' && *s <= '9')
> +       s++;
> +
> +      /* Consume optional '_'.  */
> +      if (*s == '_')
> +       s++;
> +
> +      /* If we got stuck, bail out.  */
> +      if (s == s_old)
> +       goto fail;
> +    }
> +
> +  /* Propagate subsets (until we reach a fixpoint).  */
> +  unsigned long n = count_extensions (hart_features);
> +  while (1)
> +    {
> +      /* Forward-propagation.  E.g.:
> +      if (hart_features->have_g == 1)
> +       {
> +         hart_features->have_i = 1;
> +         ...
> +         hart_features->have_zifencei = 1;
> +       }  */
> +#define ISA_EXT_GROUP_HEAD(y)                                          \
> +      if (hart_features->have_##y)                                     \
> +       {
> +#define ISA_EXT_GROUP_SUBSET(s)                                                \
> +         hart_features->have_##s = 1;
> +#define ISA_EXT_GROUP_TAIL(z)                                          \
> +       }
> +#define ISA_EXT_GROUP(x, ...)                                          \
> +       ISA_EXT_GROUP_HEAD (x)                                          \
> +       FOR_EACH (ISA_EXT_GROUP_SUBSET, __VA_ARGS__)                    \
> +       ISA_EXT_GROUP_TAIL (x)
> +#include "isa-extensions.def"
> +#undef ISA_EXT_GROUP_HEAD
> +#undef ISA_EXT_GROUP_SUBSET
> +#undef ISA_EXT_GROUP_TAIL
> +
> +      /* Backward-propagation.  E.g.:
> +      if (1
> +         && hart_features->have_i == 1
> +         ...
> +         && hart_features->have_zifencei == 1
> +         )
> +       hart_features->have_g = 1;  */
> +#define ISA_EXT_GROUP_HEAD(y)                                          \
> +      if (1
> +#define ISA_EXT_GROUP_SUBSET(s)                                                \
> +         && hart_features->have_##s == 1
> +#define ISA_EXT_GROUP_TAIL(z)                                          \
> +         )                                                             \
> +       hart_features->have_##z = 1;
> +#define ISA_EXT_GROUP(x, ...)                                          \
> +       ISA_EXT_GROUP_HEAD (x)                                          \
> +       FOR_EACH (ISA_EXT_GROUP_SUBSET, __VA_ARGS__)                    \
> +       ISA_EXT_GROUP_TAIL (x)
> +#include "isa-extensions.def"
> +#undef ISA_EXT_GROUP_HEAD
> +#undef ISA_EXT_GROUP_SUBSET
> +#undef ISA_EXT_GROUP_TAIL
> +
> +      unsigned long n2 = count_extensions (hart_features);
> +      /* Stop if fix-point reached.  */
> +      if (n == n2)
> +       break;
> +      n = n2;
> +    }
> +
> +end:
> +  return;
> +
> +fail:
> +  hart_features->rt_march = NULL;
> +}
> +
> +/* Parse RISCV_RT_CBOM_BLOCKSIZE and store value.  */
> +static inline void
> +parse_rt_cbom_blocksize (struct hart_features *hart_features)
> +{
> +  hart_features->rt_cbom_blocksize = NULL;
> +  hart_features->cbom_blocksize = 0;
> +
> +  const char *s = simple_getenv ("RISCV_RT_CBOM_BLOCKSIZE");
> +  if (s == NULL)
> +    return;
> +
> +  uint64_t v = _dl_strtoul (s, NULL);
> +  if (!is_power_of_two (v))
> +    return;
> +
> +  hart_features->rt_cbom_blocksize = s;
> +  hart_features->cbom_blocksize = v;
> +}
> +
> +/* Parse RISCV_RT_CBOZ_BLOCKSIZE and store value.  */
> +static inline void
> +parse_rt_cboz_blocksize (struct hart_features *hart_features)
> +{
> +  hart_features->rt_cboz_blocksize = NULL;
> +  hart_features->cboz_blocksize = 0;
> +
> +  const char *s = simple_getenv ("RISCV_RT_CBOZ_BLOCKSIZE");
> +  if (s == NULL)
> +    return;
> +
> +  uint64_t v = _dl_strtoul (s, NULL);
> +  if (!is_power_of_two (v))
> +    return;
> +
> +  hart_features->rt_cboz_blocksize = s;
> +  hart_features->cboz_blocksize = v;
> +}
> +
>  /* Discover hart features and store them.  */
>  static inline void
>  init_hart_features (struct hart_features *hart_features)
>  {
>    simple_memset (hart_features, 0, sizeof (*hart_features));
> +  parse_rt_march (hart_features);
> +  parse_rt_cbom_blocksize (hart_features);
> +  parse_rt_cboz_blocksize (hart_features);
>  }
> diff --git a/sysdeps/unix/sysv/linux/riscv/macro-for-each.h b/sysdeps/unix/sysv/linux/riscv/macro-for-each.h
> new file mode 100644
> index 0000000000..524bef3c0a
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/riscv/macro-for-each.h
> @@ -0,0 +1,24 @@
> +/* Recursive macros implementation by David Mazières
> +   https://www.scs.stanford.edu/~dm/blog/va-opt.html  */
> +
> +#ifndef _MACRO_FOR_EACH_H
> +#define _MACRO_FOR_EACH_H
> +
> +#define EXPAND1(...) __VA_ARGS__
> +#define EXPAND2(...) EXPAND1 (EXPAND1 (EXPAND1 (EXPAND1 (__VA_ARGS__))))
> +#define EXPAND3(...) EXPAND2 (EXPAND2 (EXPAND2 (EXPAND2 (__VA_ARGS__))))
> +#define EXPAND4(...) EXPAND3 (EXPAND3 (EXPAND3 (EXPAND3 (__VA_ARGS__))))
> +#define EXPAND(...)  EXPAND4 (EXPAND4 (EXPAND4 (EXPAND4 (__VA_ARGS__))))
> +
> +#define FOR_EACH(macro, ...)                                           \
> +  __VA_OPT__ (EXPAND (FOR_EACH_HELPER (macro, __VA_ARGS__)))
> +
> +#define PARENS ()
> +
> +#define FOR_EACH_HELPER(macro, a1, ...)                                        \
> +  macro (a1)                                                           \
> +  __VA_OPT__ (FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
> +
> +#define FOR_EACH_AGAIN() FOR_EACH_HELPER
> +
> +#endif /* _MACRO_FOR_EACH_H  */
> --
> 2.39.1
>
  

Patch

diff --git a/sysdeps/unix/sysv/linux/riscv/hart-features.c b/sysdeps/unix/sysv/linux/riscv/hart-features.c
index 41111eff57..6de41a26cc 100644
--- a/sysdeps/unix/sysv/linux/riscv/hart-features.c
+++ b/sysdeps/unix/sysv/linux/riscv/hart-features.c
@@ -17,12 +17,17 @@ 
    <https://www.gnu.org/licenses/>.  */
 
 #include <hart-features.h>
+#include <macro-for-each.h>
+#include <string_private.h>
 
 /* The code in this file is executed very early, so we cannot call
    indirect functions because ifunc support is not initialized.
    Therefore this file adds a few simple helper functions to avoid
    dependencies to functions outside of this file.  */
 
+#define xstr(s) str(s)
+#define str(s) #s
+
 static inline void
 inhibit_loop_to_libcall
 simple_memset (void *s, int c, size_t n)
@@ -35,9 +40,298 @@  simple_memset (void *s, int c, size_t n)
     }
 }
 
+static inline size_t
+inhibit_loop_to_libcall
+simple_strlen (const char *s)
+{
+  size_t n = 0;
+  char c = *s;
+  while (c != 0)
+    {
+      s++;
+      n++;
+      c = *s;
+    }
+  return n;
+}
+
+static inline int
+inhibit_loop_to_libcall
+simple_strncmp (const char *s1, const char *s2, size_t n)
+{
+  while (n != 0)
+    {
+      if (*s1 == 0 || *s1 != *s2)
+	return *((const unsigned char *)s1) - *((const unsigned char *)s2);
+      n--;
+      s1++;
+      s2++;
+    }
+  return 0;
+}
+
+extern char **__environ;
+static inline char*
+simple_getenv (const char *name)
+{
+  char **ep;
+  uint16_t name_start;
+
+  if (__environ == NULL || name[0] == 0 || name[1] == 0)
+    return NULL;
+
+  size_t len = simple_strlen (name);
+#if _STRING_ARCH_unaligned
+  name_start = *(const uint16_t *) name;
+#else
+  name_start = (((const unsigned char *) name)[0]
+		| (((const unsigned char *) name)[1] << 8));
+#endif
+  len -= 2;
+  name += 2;
+
+  for (ep = __environ; *ep != NULL; ++ep)
+    {
+#if _STRING_ARCH_unaligned
+      uint16_t ep_start = *(uint16_t *) *ep;
+#else
+      uint16_t ep_start = (((unsigned char *) *ep)[0]
+			   | (((unsigned char *) *ep)[1] << 8));
+#endif
+      if (name_start == ep_start && !simple_strncmp (*ep + 2, name, len)
+	  && (*ep)[len + 2] == '=')
+	return &(*ep)[len + 3];
+    }
+  return NULL;
+}
+
+/* Check if the given number is a power of 2.
+   Return true if so, or false otherwise.  */
+static inline int
+is_power_of_two (unsigned long v)
+{
+  return (v & (v - 1)) == 0;
+}
+
+/* Check if the given string str starts with
+   the prefix pre.  Return true if so, or false
+   otherwise.  */
+static inline int
+starts_with (const char *str, const char *pre)
+{
+  return simple_strncmp (pre, str, simple_strlen (pre)) == 0;
+}
+
+/* Lower all characters of a string up to the
+   first NUL-character in the string.  */
+static inline void
+strtolower (char *s)
+{
+  char c = *s;
+  while (c != '\0')
+    {
+      if (c >= 'A' && c <= 'Z')
+	*s = c + 'a' - 'A';
+      s++;
+      c = *s;
+    }
+}
+
+/* Count the number of detected extensions.  */
+static inline unsigned long
+count_extensions (struct hart_features *hart_features)
+{
+  unsigned long n = 0;
+#define ISA_EXT(e)							\
+  if (hart_features->have_##e == 1)					\
+    n++;
+#define ISA_EXT_GROUP(g, ...)						\
+  if (hart_features->have_##g == 1)					\
+    n++;
+#include "isa-extensions.def"
+  return n;
+}
+
+/* Check if the given charater is not '0'-'9'.  */
+static inline int
+notanumber (const char c)
+{
+  return (c < '0' || c > '9');
+}
+
+/* Parse RISCV_RT_MARCH and store found extensions.  */
+static inline void
+parse_rt_march (struct hart_features *hart_features)
+{
+  const char* s = simple_getenv ("RISCV_RT_MARCH");
+  if (s == NULL)
+    goto end;
+
+  hart_features->rt_march = s;
+
+  /* "RISC-V ISA strings begin with either RV32I, RV32E, RV64I, or RV128I
+      indicating the supported address space size in bits for the base
+      integer ISA."  */
+  if (starts_with (s, "rv32") && notanumber (*(s+4)))
+    {
+      hart_features->xlen = 32;
+      s += 4;
+    }
+  else if (starts_with (s, "rv64") && notanumber (*(s+4)))
+    {
+      hart_features->xlen = 64;
+      s += 4;
+    }
+  else if (starts_with (s, "rv128") && notanumber (*(s+5)))
+    {
+      hart_features->xlen = 128;
+      s += 5;
+    }
+  else
+    {
+      goto fail;
+    }
+
+  /* Parse the extensions.  */
+  const char *s_old = s;
+  while (*s != '\0')
+    {
+#define ISA_EXT(e)							\
+      else if (starts_with (s, xstr (e)))				\
+	{								\
+	  hart_features->have_##e = 1;					\
+	  s += simple_strlen (xstr (e));				\
+	}
+#define ISA_EXT_GROUP(g, ...)						\
+      ISA_EXT (g)
+      if (0);
+#include "isa-extensions.def"
+
+      /* Consume optional version information.  */
+      while (*s >= '0' && *s <= '9')
+	s++;
+      while (*s == 'p')
+	s++;
+      while (*s >= '0' && *s <= '9')
+	s++;
+
+      /* Consume optional '_'.  */
+      if (*s == '_')
+	s++;
+
+      /* If we got stuck, bail out.  */
+      if (s == s_old)
+	goto fail;
+    }
+
+  /* Propagate subsets (until we reach a fixpoint).  */
+  unsigned long n = count_extensions (hart_features);
+  while (1)
+    {
+      /* Forward-propagation.  E.g.:
+      if (hart_features->have_g == 1)
+	{
+	  hart_features->have_i = 1;
+	  ...
+	  hart_features->have_zifencei = 1;
+	}  */
+#define ISA_EXT_GROUP_HEAD(y)						\
+      if (hart_features->have_##y)					\
+	{
+#define ISA_EXT_GROUP_SUBSET(s)						\
+	  hart_features->have_##s = 1;
+#define ISA_EXT_GROUP_TAIL(z)						\
+	}
+#define ISA_EXT_GROUP(x, ...)						\
+	ISA_EXT_GROUP_HEAD (x)						\
+	FOR_EACH (ISA_EXT_GROUP_SUBSET, __VA_ARGS__)			\
+	ISA_EXT_GROUP_TAIL (x)
+#include "isa-extensions.def"
+#undef ISA_EXT_GROUP_HEAD
+#undef ISA_EXT_GROUP_SUBSET
+#undef ISA_EXT_GROUP_TAIL
+
+      /* Backward-propagation.  E.g.:
+      if (1
+	  && hart_features->have_i == 1
+	  ...
+	  && hart_features->have_zifencei == 1
+	  )
+	hart_features->have_g = 1;  */
+#define ISA_EXT_GROUP_HEAD(y)						\
+      if (1
+#define ISA_EXT_GROUP_SUBSET(s)						\
+	  && hart_features->have_##s == 1
+#define ISA_EXT_GROUP_TAIL(z)						\
+	  )								\
+	hart_features->have_##z = 1;
+#define ISA_EXT_GROUP(x, ...)						\
+	ISA_EXT_GROUP_HEAD (x)						\
+	FOR_EACH (ISA_EXT_GROUP_SUBSET, __VA_ARGS__)			\
+	ISA_EXT_GROUP_TAIL (x)
+#include "isa-extensions.def"
+#undef ISA_EXT_GROUP_HEAD
+#undef ISA_EXT_GROUP_SUBSET
+#undef ISA_EXT_GROUP_TAIL
+
+      unsigned long n2 = count_extensions (hart_features);
+      /* Stop if fix-point reached.  */
+      if (n == n2)
+	break;
+      n = n2;
+    }
+
+end:
+  return;
+
+fail:
+  hart_features->rt_march = NULL;
+}
+
+/* Parse RISCV_RT_CBOM_BLOCKSIZE and store value.  */
+static inline void
+parse_rt_cbom_blocksize (struct hart_features *hart_features)
+{
+  hart_features->rt_cbom_blocksize = NULL;
+  hart_features->cbom_blocksize = 0;
+
+  const char *s = simple_getenv ("RISCV_RT_CBOM_BLOCKSIZE");
+  if (s == NULL)
+    return;
+
+  uint64_t v = _dl_strtoul (s, NULL);
+  if (!is_power_of_two (v))
+    return;
+
+  hart_features->rt_cbom_blocksize = s;
+  hart_features->cbom_blocksize = v;
+}
+
+/* Parse RISCV_RT_CBOZ_BLOCKSIZE and store value.  */
+static inline void
+parse_rt_cboz_blocksize (struct hart_features *hart_features)
+{
+  hart_features->rt_cboz_blocksize = NULL;
+  hart_features->cboz_blocksize = 0;
+
+  const char *s = simple_getenv ("RISCV_RT_CBOZ_BLOCKSIZE");
+  if (s == NULL)
+    return;
+
+  uint64_t v = _dl_strtoul (s, NULL);
+  if (!is_power_of_two (v))
+    return;
+
+  hart_features->rt_cboz_blocksize = s;
+  hart_features->cboz_blocksize = v;
+}
+
 /* Discover hart features and store them.  */
 static inline void
 init_hart_features (struct hart_features *hart_features)
 {
   simple_memset (hart_features, 0, sizeof (*hart_features));
+  parse_rt_march (hart_features);
+  parse_rt_cbom_blocksize (hart_features);
+  parse_rt_cboz_blocksize (hart_features);
 }
diff --git a/sysdeps/unix/sysv/linux/riscv/macro-for-each.h b/sysdeps/unix/sysv/linux/riscv/macro-for-each.h
new file mode 100644
index 0000000000..524bef3c0a
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/riscv/macro-for-each.h
@@ -0,0 +1,24 @@ 
+/* Recursive macros implementation by David Mazières
+   https://www.scs.stanford.edu/~dm/blog/va-opt.html  */
+
+#ifndef _MACRO_FOR_EACH_H
+#define _MACRO_FOR_EACH_H
+
+#define EXPAND1(...) __VA_ARGS__
+#define EXPAND2(...) EXPAND1 (EXPAND1 (EXPAND1 (EXPAND1 (__VA_ARGS__))))
+#define EXPAND3(...) EXPAND2 (EXPAND2 (EXPAND2 (EXPAND2 (__VA_ARGS__))))
+#define EXPAND4(...) EXPAND3 (EXPAND3 (EXPAND3 (EXPAND3 (__VA_ARGS__))))
+#define EXPAND(...)  EXPAND4 (EXPAND4 (EXPAND4 (EXPAND4 (__VA_ARGS__))))
+
+#define FOR_EACH(macro, ...)						\
+  __VA_OPT__ (EXPAND (FOR_EACH_HELPER (macro, __VA_ARGS__)))
+
+#define PARENS ()
+
+#define FOR_EACH_HELPER(macro, a1, ...)					\
+  macro (a1)								\
+  __VA_OPT__ (FOR_EACH_AGAIN PARENS (macro, __VA_ARGS__))
+
+#define FOR_EACH_AGAIN() FOR_EACH_HELPER
+
+#endif /* _MACRO_FOR_EACH_H  */