[RFC PATCH 3/5] riscv: libmvec: add benchtests
Yao Zihong
zihong.plct@isrc.iscas.ac.cn
Sun Feb 8 22:04:38 GMT 2026
From: Zihong Yao <zihong.plct@isrc.iscas.ac.cn>
Add RISC-V libmvec benchtests for the benchtests subdir.
Wire the bench targets into the build, add RVV-specific CFLAGS,
and provide an RVV capability check plus a generator script that
emits per-function bench sources.
Signed-off-by: Zihong Yao <zihong.plct@isrc.iscas.ac.cn>
---
sysdeps/riscv/rvd/Makeconfig | 12 +
sysdeps/riscv/rvd/Makefile | 54 ++++
sysdeps/riscv/rvd/bench-libmvec-arch.h | 49 +++
.../riscv/rvd/scripts/bench_libmvec_rvv.py | 293 ++++++++++++++++++
4 files changed, 408 insertions(+)
create mode 100644 sysdeps/riscv/rvd/bench-libmvec-arch.h
create mode 100644 sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py
diff --git a/sysdeps/riscv/rvd/Makeconfig b/sysdeps/riscv/rvd/Makeconfig
index 84649c6500..3c1fac4316 100644
--- a/sysdeps/riscv/rvd/Makeconfig
+++ b/sysdeps/riscv/rvd/Makeconfig
@@ -65,3 +65,15 @@ $(common-objpfx)libmvec.mk: $(common-objpfx)config.make
done; \
echo "endif";)> $@T
mv -f $@T $@
+
+# Define libmvec function for benchtests directory.
+libmvec-double-bench-funcs = \
+ log \
+
+libmvec-float-bench-funcs = \
+
+bench-libmvec-double = \
+ $(addprefix double-, $(libmvec-double-bench-funcs)) \
+
+bench-libmvec-float = \
+ $(addprefix float-, $(libmvec-float-bench-funcs)) \
diff --git a/sysdeps/riscv/rvd/Makefile b/sysdeps/riscv/rvd/Makefile
index 7f409e3f02..3abcb8b913 100644
--- a/sysdeps/riscv/rvd/Makefile
+++ b/sysdeps/riscv/rvd/Makefile
@@ -24,6 +24,48 @@ endif
endif
+ifeq ($(build-mathvec),yes)
+
+bench-libmvec := $(bench-libmvec-double) $(bench-libmvec-float)
+
+ifeq (${STATIC-BENCHTESTS},yes)
+libmvec-benchtests = $(common-objpfx)mathvec/libmvec.a $(common-objpfx)math/libm.a
+else
+libmvec-benchtests = $(libmvec) $(libm)
+endif
+
+$(addprefix $(objpfx)bench-,$(bench-libmvec)): $(libmvec-benchtests)
+$(foreach b,$(bench-libmvec),$(eval LDLIBS-bench-$(b) += $(libmvec)))
+
+bench-libmvec-deps = $(..)benchtests/bench-libmvec-skeleton.c \
+ $(..)sysdeps/riscv/rvd/bench-libmvec-arch.h \
+ $(..)sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py \
+ bench-timing.h Makefile
+
+$(objpfx)bench-float-%.c: $(bench-libmvec-deps)
+ $(PYTHON) $(..)sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py $(basename $(@F)) > $@-tmp
+ mv -f $@-tmp $@
+
+$(objpfx)bench-double-%.c: $(bench-libmvec-deps)
+ $(PYTHON) $(..)sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py $(basename $(@F)) > $@-tmp
+ mv -f $@-tmp $@
+
+define riscv64-bench-vector-cflags-template
+CFLAGS-bench-$(1).c += -march=rv64gcv -Wno-missing-prototypes -Wno-undef
+endef
+
+define riscv32-bench-vector-cflags-template
+CFLAGS-bench-$(1).c += -march=rv32gcv -Wno-missing-prototypes -Wno-undef
+endef
+
+ifeq ($(config-machine),riscv64)
+$(foreach b,$(bench-libmvec), $(eval $(call riscv64-bench-vector-cflags-template,$(b))))
+endif
+
+ifeq ($(config-machine),riscv32)
+$(foreach b,$(bench-libmvec), $(eval $(call riscv32-bench-vector-cflags-template,$(b))))
+endif
+
ifneq ($(multi-arch),no)
ifeq ($(subdir)$(build-mathvec),mathyes)
@@ -36,4 +78,16 @@ endif
endif
+ifeq ($(subdir),benchtests)
+
+ifeq ($(build-mathvec),yes)
+
+bench-libmvec := $(bench-libmvec-float) $(bench-libmvec-double)
+
+endif
+
+endif
+
+endif
+
endif
diff --git a/sysdeps/riscv/rvd/bench-libmvec-arch.h b/sysdeps/riscv/rvd/bench-libmvec-arch.h
new file mode 100644
index 0000000000..41181206dc
--- /dev/null
+++ b/sysdeps/riscv/rvd/bench-libmvec-arch.h
@@ -0,0 +1,49 @@
+/* Runtime architecture check for libmvec benchtests. RISC-V version.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/hwprobe.h>
+
+#define INIT_ARCH() \
+ do \
+ { \
+ if (!supported ()) \
+ return 77; \
+ } \
+ while (0)
+
+static bool
+supported (void)
+{
+ struct riscv_hwprobe pair = { .key = RISCV_HWPROBE_KEY_IMA_EXT_0 };
+ if (__riscv_hwprobe (&pair, 1, 0, NULL, 0) != 0)
+ {
+ printf ("__riscv_hwprobe failed.\n");
+ return false;
+ }
+
+ if ((pair.value & RISCV_HWPROBE_IMA_V) == 0)
+ {
+ printf ("RVV not supported.\n");
+ return false;
+ }
+
+ return true;
+}
diff --git a/sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py b/sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py
new file mode 100644
index 0000000000..45b2f70a52
--- /dev/null
+++ b/sysdeps/riscv/rvd/scripts/bench_libmvec_rvv.py
@@ -0,0 +1,293 @@
+#!/usr/bin/python3
+# Copyright (C) 2026 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+"""Generate RVV libmvec bench source that exercises all exported variants."""
+
+import re
+import sys
+from pathlib import Path
+from typing import Dict, Iterable, Iterator, List, Tuple, Union
+
+TEMPLATE = r"""
+#include <math.h>
+#include <riscv_vector.h>
+
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+struct args_1
+{{
+ {stype} arg0[{nelems}];
+}};
+
+struct variant
+{{
+ const char *name;
+ size_t stride;
+ double (*call) (const struct args_1 *, size_t idx);
+ const struct args_1 *in;
+}};
+
+static const struct args_1 in0 = {{
+{in_data}
+}};
+
+{protos}
+
+{callers}
+
+static const struct variant variants[] = {{
+{variant_table}
+}};
+
+#define NUM_VARIANTS (sizeof (variants) / sizeof (variants[0]))
+#define NUM_SAMPLES(i) ({nelems} / variants[i].stride)
+#define VARIANT(i) (variants[i].name)
+#define STRIDE (variants[v].stride)
+
+static double volatile ret;
+
+#define BENCH_FUNC(i, j) ({{ ret = variants[i].call (variants[i].in, (j) * variants[i].stride); }})
+#define FUNCNAME "{func}"
+
+#include <bench-libmvec-skeleton.c>
+"""
+
+
+def _parse_variants_macro(lines: List[str], macro: str) -> List[Tuple[int, int]]:
+ variants: List[Tuple[int, int]] = []
+ in_macro = False
+ for line in lines:
+ if line.startswith(f"#define {macro}"):
+ in_macro = True
+ if not in_macro:
+ continue
+ for lmul, simdlen in re.findall(r"X\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)", line):
+ variants.append((int(lmul), int(simdlen)))
+ if in_macro and not line.rstrip().endswith("\\"):
+ in_macro = False
+ if variants:
+ break
+ if not variants:
+ raise SystemExit(f"failed to parse {macro} from v_math_variants.h")
+ return variants
+
+
+def _load_variants_from_header() -> Dict[str, List[Tuple[int, int]]]:
+ header = Path(__file__).resolve().parent.parent / "v_math_variants.h"
+ try:
+ lines = header.read_text(encoding="utf-8").splitlines()
+ except OSError as exc:
+ raise SystemExit(f"cannot read {header}: {exc}") from exc
+
+ return {
+ "double": _parse_variants_macro(lines, "RVV_LIBMVEC_VARIANTS_F64"),
+ "float": _parse_variants_macro(lines, "RVV_LIBMVEC_VARIANTS_F32"),
+ }
+
+
+VARIANTS = _load_variants_from_header()
+
+PRECISION_TABLE = {
+ "double": {
+ "stype": "double",
+ "elem_width": 64,
+ "variants": VARIANTS["double"],
+ },
+ "float": {
+ "stype": "float",
+ "elem_width": 32,
+ "variants": VARIANTS["float"],
+ },
+}
+
+Variant = Dict[str, Union[str, int]]
+
+
+def _make_vector_variants(
+ func: str, elem_width: int, variants: Iterable[Tuple[int, int]]
+) -> Iterator[Variant]:
+ """Yield vector variant metadata for the given function."""
+ vtype_prefix = f"vfloat{elem_width}"
+ setvl_prefix = f"e{elem_width}"
+ for lmul, simdlen in variants:
+ m_suffix = f"m{lmul}"
+ vtype = f"{vtype_prefix}{m_suffix}_t"
+ setvl_suffix = f"{setvl_prefix}{m_suffix}"
+ asm_sym = f"_ZGVr{lmul}N{simdlen}v_{func}"
+ yield {
+ "name": asm_sym,
+ "vtype": vtype,
+ "setvl_suffix": setvl_suffix,
+ "simdlen": simdlen,
+ "asm_sym": asm_sym,
+ "kind": "vector",
+ }
+
+
+def _parse_bench_name(name: str) -> Tuple[str, str]:
+ """Parse a bench target name into (precision, function)."""
+ # Accept names like "double-log" (bench list) or "bench-double-log"
+ # (target name in benchtests).
+ if name.startswith("bench-"):
+ name = name[len("bench-"):]
+
+ parts = name.split("-")
+ if len(parts) < 2:
+ raise SystemExit(f"unexpected name format: {name}")
+
+ prec = parts[0]
+ func = "_".join(parts[1:])
+ return prec, func
+
+
+def _load_input_data(func: str) -> Tuple[str, int]:
+ """Load inputs for FUNC, returning the initializer and element count."""
+ input_filename = f"{func}-inputs"
+ with open(
+ f"../benchtests/libmvec/{input_filename}",
+ encoding="utf-8",
+ ) as f:
+ input_file = f.readlines()
+
+ arity_line = [l for l in input_file if l.startswith("## args: ")]
+ if not arity_line:
+ raise SystemExit("missing arity line in input file")
+
+ in_vals = (l.strip() for l in input_file if l and not l.startswith("#"))
+ in_vals = (l.split(", ") for l in in_vals)
+ in_vals = list(zip(*in_vals))
+ in_data = ",\n".join(
+ "{" + (", ".join(val for val in col) + "}") for col in in_vals
+ )
+ return in_data, len(in_vals[0])
+
+
+def _vector_variant_snippets(
+ idx: int,
+ variant: Variant,
+) -> Tuple[str, str, str]:
+ """Build prototype, caller, and table entry for a vector variant."""
+ vtype = variant["vtype"]
+ setvl_suffix = variant["setvl_suffix"]
+ simdlen = variant["simdlen"]
+ asm_sym = variant["asm_sym"]
+ name_sym = variant["name"]
+
+ if "float64" in vtype:
+ elem_width = "64"
+ elem_type = "f64"
+ elif "float32" in vtype:
+ elem_width = "32"
+ elem_type = "f32"
+ else:
+ raise SystemExit(f"unsupported vtype: {vtype}")
+
+ m_suffix = vtype.replace(f"vfloat{elem_width}", "").replace("_t", "")
+
+ proto = (
+ f"extern {vtype} {name_sym}({vtype}) __asm__(\"{asm_sym}\");"
+ )
+ caller = (
+ f"static double\n"
+ f"call_{idx} (const struct args_1 *in, size_t idx)\n"
+ f"{{\n"
+ f" size_t vl = __riscv_vsetvl_{setvl_suffix} ({simdlen});\n"
+ f" {vtype} x = "
+ f"__riscv_vle{elem_width}_v_{elem_type}{m_suffix} "
+ f"(&in->arg0[idx], vl);\n"
+ f" {vtype} r = {name_sym} (x);\n"
+ f" return __riscv_vfmv_f_s_{elem_type}{m_suffix}_{elem_type} "
+ f"(r);\n"
+ f"}}\n"
+ )
+ entry = f' {{"{asm_sym}", {simdlen}, call_{idx}, &in0}}'
+ return proto, caller, entry
+
+
+def _build_variants(
+ func: str,
+ elem_width: int,
+ variants: Iterable[Tuple[int, int]],
+) -> Tuple[List[str], List[str], List[str]]:
+ """Build prototypes, callers, and table entries for all variants."""
+ protos: List[str] = []
+ callers: List[str] = []
+ variant_entries: List[str] = []
+
+ variants_list = list(_make_vector_variants(func, elem_width, variants))
+ variants_list.append(
+ {
+ "name": "scalar",
+ "asm_sym": func,
+ "simdlen": 1,
+ "kind": "scalar",
+ }
+ )
+
+ for idx, variant in enumerate(variants_list):
+ if variant["kind"] == "scalar":
+ callers.append(
+ f"static double\n"
+ f"call_{idx} (const struct args_1 *in, size_t idx)\n"
+ f"{{\n"
+ f" return {func} (in->arg0[idx]);\n"
+ f"}}\n"
+ )
+ variant_entries.append(
+ f' {{"{variant["asm_sym"]}", 1, call_{idx}, &in0}}'
+ )
+ continue
+
+ proto, caller, entry = _vector_variant_snippets(idx, variant)
+ protos.append(proto)
+ callers.append(caller)
+ variant_entries.append(entry)
+
+ return protos, callers, variant_entries
+
+def main(name: str):
+ prec, func = _parse_bench_name(name)
+
+ config = PRECISION_TABLE.get(prec)
+ if not config:
+ raise SystemExit(f"unsupported precision: {prec}")
+
+ stype = config["stype"]
+ elem_width = config["elem_width"]
+ in_data, nelems = _load_input_data(func)
+ protos, callers, variant_entries = _build_variants(
+ func,
+ elem_width,
+ config["variants"],
+ )
+
+ print(
+ TEMPLATE.format(
+ stype=stype,
+ nelems=nelems,
+ in_data=in_data,
+ protos="\n".join(protos),
+ callers="\n".join(callers),
+ variant_table=",\n".join(variant_entries),
+ func=func,
+ )
+ )
+
+
+if __name__ == "__main__":
+ main(sys.argv[1])
--
2.47.3
More information about the Libc-alpha
mailing list