Archdetect riscv update - #243
Conversation
|
I think this update makes sense as it certainly gives more flexibility and would allow working with subsets of extensions instead of matching the whole ISA string. I suggest to change the comment in as it explains better why the replacement is done. The tokens here are just features/extensions and do not translate to compilation flags (in contrast with x86). At the end, when building we will need the ISA string (with underscores) anyway to pass it to the Which makes me think... if we do: where/how is the ISA string to pass to |
|
New job on instance
|
llvm: Supported profile names can be passed using -march instead of a standard ISA naming string. Currently supported profiles: GCC: -march=[ISA|Profile|Profile_ISA|processor-string] so if we use the profile name as the naming of the cpu, it can directly be used in the compilers... |
I'm not sure if I understand your question correctly, but: in principle, all our builds use the default value for EasyBuild's So yeah, making |
…tect-riscv Use sifive/u74-mc path name, port cpuinfo fixtures, and fix p550 CI test paths. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Folded the remaining useful bits from #234 into this PR:
Tokenized ISA matching from this PR is kept. Local RISC-V archdetect tests for p550, u74-mc, and both SpacemiT cases pass. Closing #234 as superseded by this PR. |
Set -march/-mtune from eessi_riscv_optarch.map in bot/build.sh and EESSI-install-software.sh so optimized RISC-V trees are not host-native-only. Co-authored-by: Cursor <cursoragent@cursor.com>
Build-time RISC-V optarch mapAdded the complementary piece to archdetect (runtime selection):
Example: |
Use EasyBuild multi-compiler optarch (GCC:;Clang:;LLVM:) and validate flag acceptance with riscv64 GCC and Clang in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
GCC + Clang/LLVM optarch coverageMap entries are now EasyBuild multi-compiler strings, e.g. u74-mc: `GCC:-march=rva20u64 -mtune=sifive-u74 -mabi=lp64d;Clang:-mcpu=sifive-u74 -mabi=lp64d;LLVM:-mcpu=sifive-u74 -mabi=lp64d` CI installs `gcc-riscv64-linux-gnu` + `clang` and runs acceptance checks for both toolchains (`EESSI_RISCV_OPTARCH_REQUIRE_COMPILERS=1`). Locally verified: all GCC and Clang/LLVM flag sets compile an empty C unit. |
b40da49 to
a3adb93
Compare
Design note: RISC-V archdetect vs build-time
|
| x86/ARM assumption | RISC-V reality |
|---|---|
-march=native exists |
It does not |
| Host ≈ EESSI target | Breaks for profiles and cross-target bot jobs |
| Spec / ISA string ≈ compiler flag | Feature tokens for matching ≠ a -march string |
| Native implies good tuning | Need explicit -mtune / -mcpu |
EasyBuild’s get_isa_riscv() (easybuild-framework#5029) only helps “build on this host for this host”. It does not know about EESSI paths like riscv64/sifive/u74-mc or riscv64/generic/rva22u64.
/proc/cpuinfo isa: is also a poor CPU identity: it varies by vendor, kernel, and board (e.g. same SiFive vendor for P550 vs U74; different X60 ISA strings across kernels). Matching the whole ISA string was brittle; tokenizing it (this PR) fixes detection, but then the spec column is no longer a ready-made -march= value — so builds need another source of flags.
Design: split runtime selection from build-time flags
-
Runtime (archdetect) —
eessi_archdetect.sh+eessi_arch_riscv.spec
Choose the software subdirectory via tokenized ISA features + vendor.
Includes vendor targets (sifive/u74-mc,sifive/p550,spacemit/x60, …) and profile paths (riscv64/generic/rva20u64,rva22u64,rva23u64). -
Build time (optarch map) —
init/arch_specs/eessi_riscv_optarch.map+init/eessi_riscv_optarch.sh
Mapsoftware_subdir→EASYBUILD_OPTARCH(EasyBuild multi-compiler form:GCC:…;Clang:…;LLVM:…).
Wired inbot/build.shandEESSI-install-software.sh(honours pre-setEASYBUILD_OPTARCH/--generic).
Same idea as GPU builds: accel/nvidia/cc80 already maps to an explicit EasyBuild setting; RISC-V CPU targets need the same for -march/-mtune.
Flag roles
-march= legal instruction set (prefer RISC-V profiles where possible:rva20u64, …)-mtune/-mcpu= microarchitecture (e.g.sifive-u74,sifive-p400-series; SpacemiT stays-mtune=genericuntil toolchains grow a name)-mabi=lp64d= keep consistent with the EESSI RISC-V Linux userspaceGENERIC= EasyBuild’s compatibility floor (-march=rv64gc -mabi=lp64d), not an optimization strategy
Example (u74-mc):
GCC:-march=rva20u64 -mtune=sifive-u74 -mabi=lp64d;Clang:-mcpu=sifive-u74 -mabi=lp64d;LLVM:-mcpu=sifive-u74 -mabi=lp64d
What this PR lands
| Piece | Role |
|---|---|
| Tokenized RISC-V ISA matching | Reliable archdetect |
sifive/u74-mc + tests (from #234) |
VisionFive 2 / U74 detection |
| Profile + vendor specs | Runtime paths |
eessi_riscv_optarch.map |
Build-time flags for GCC + Clang/LLVM |
| CI for map + compiler acceptance | Guard the mapping |
Happy to adjust map entries (especially SpacemiT -march strings) based on review.
Profile path names stay as rva20/22/23u64, but -march now uses explicit rv64* ISA strings so GCC 13 / Clang 18 accept the flags. Drop unsupported -mtune=generic and S-mode extensions from SpacemiT entries. Co-authored-by: Cursor <cursoragent@cursor.com>
Use GCC 14.3.0 and llvm-compilers 20.1.8 from dev.eessi.io/riscv in CI (via CVMFS + qemu), print --version, and assert those versions. Co-authored-by: Cursor <cursoragent@cursor.com>
GCC was resolving the host assembler after the module-load step, so -march=rv64gc failed while Clang (integrated asm) passed. Persist module PATH / toolchain bins and smoke-check as before the map tests. Co-authored-by: Cursor <cursoragent@cursor.com>
| elif [[ ${EESSI_SOFTWARE_SUBDIR_OVERRIDE} == riscv64/* ]]; then | ||
| # Export mapped -march/-mtune into the container; install script also applies | ||
| # the map if unset. See init/arch_specs/eessi_riscv_optarch.map | ||
| # shellcheck source=init/eessi_riscv_optarch.sh | ||
| source "${software_layer_dir}/init/eessi_riscv_optarch.sh" | ||
| if optarch=$(eessi_riscv_optarch_for "${EESSI_SOFTWARE_SUBDIR_OVERRIDE}"); then | ||
| if [[ "${optarch}" == "GENERIC" ]]; then | ||
| INSTALL_SCRIPT_ARGS+=("--generic") | ||
| else | ||
| export EASYBUILD_OPTARCH="${optarch}" | ||
| echo "bot/build.sh: EASYBUILD_OPTARCH='${EASYBUILD_OPTARCH}' (from RISC-V optarch map)" | ||
| fi | ||
| else | ||
| echo "bot/build.sh: ERROR: no RISC-V optarch mapping for '${EESSI_SOFTWARE_SUBDIR_OVERRIDE}'" >&2 | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
I think it would be better/nicer if this is done by EESSI-extend. That way, both the bot and EESSI users doing builds on top of EESSI will get the right optimization flags when loading EESSI-extend. We sort of do the same for NVIDIA and AMD compute capabilities (https://github.com/EESSI/software-layer-scripts/blob/main/EESSI-extend-easybuild.eb#L216).
There was a problem hiding this comment.
Good call — done in d6d5ad1.
RISC-V EASYBUILD_OPTARCH is now set from init/arch_specs/eessi_riscv_optarch.map when loading EESSI-extend (same idea as CUDA/AMD compute capabilities): if EESSI_SOFTWARE_SUBDIR is riscv64/* and EASYBUILD_OPTARCH is not already set (e.g. --generic), the module looks up the map and setenvs it.
Removed the duplicate wiring from bot/build.sh and EESSI-install-software.sh. The shell helper init/eessi_riscv_optarch.sh stays for tests / optional direct use.
So both the bot and users extending EESSI get the mapped -march/-mtune/-mcpu flags via module load EESSI-extend.
Avoid invoking the assembler under qemu on x86, where PATH often hits the x86_64 Gentoo as and rejects -march=rv64*. Flag acceptance still uses EESSI GCC 14.3.0 and Clang 20.1.8. Co-authored-by: Cursor <cursoragent@cursor.com>
Apply the software_subdir → optarch map when loading EESSI-extend (same pattern as CUDA/AMD compute capabilities), so bot and user builds both get the flags. Drop the duplicate wiring from bot/build.sh and the install script. Co-authored-by: Cursor <cursoragent@cursor.com>
HaoZeke
left a comment
There was a problem hiding this comment.
The tokenization direction looks right to me and the vendored targets (p550, u74-mc, x60, x60-k6.6) are consistent between spec and map.
The generic profile entries are the problem: I don't think they can match at all under the vendor gate, one has a malformed token, and the specs are weaker than what the map compiles with; details inline.
On scope I'd split the optarch map + EESSI-extend Lua + its CI out, so the archdetect fix can merge while the optarch design gets its own discussion.
The map also has two parsers now, bash and Lua, which will drift.
| "riscv64/spacemit/x60-k6.6" "0x710" "rv64imafdcv_zicbom_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zfhmin_zca_zcd_zba_zbb_zbc_zbs_zkt_zve32f_zve32x_zve64d_zve64f_zve64x_zvfh_zvfhmin_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt" # Banana Pi F3 k6.6 | ||
| "riscv64/generic/rva20u64" "" "rv64imafdc" | ||
| "riscv64/generic/rva22u64" "" "rv64imafdc zfhmin" | ||
| "riscv64/generic/rva23u64" "" "rv64imafdciv zfhmin" |
There was a problem hiding this comment.
rv64imafdciv has i twice, and the kernel prints the single-letter block in canonical order (1, "ISA string ordering rules"; the canonical order itself is Appendix C of the ISA manual 2), so /proc/cpuinfo can never produce ...dciv.
Should be rv64imafdcv I think.
Doesn't matter anyway right now because of the vendor gate, but it'll be a silent never-match once that's fixed.
|
|
||
| riscv64/generic GENERIC | ||
| riscv64/generic/rva20u64 GCC:-march=rv64gc -mabi=lp64d;Clang:-march=rv64gc -mabi=lp64d;LLVM:-march=rv64gc -mabi=lp64d | ||
| riscv64/generic/rva22u64 GCC:-march=rv64gc_zba_zbb_zbs_zfhmin_zicbom_zicbop_zicboz -mabi=lp64d;Clang:-march=rv64gc_zba_zbb_zbs_zfhmin_zicbom_zicbop_zicboz -mabi=lp64d;LLVM:-march=rv64gc_zba_zbb_zbs_zfhmin_zicbom_zicbop_zicboz -mabi=lp64d |
There was a problem hiding this comment.
The -march here looks right to me (RVA22U64 implies zba/zbb/zbs/zicbo* 1), but the spec entry for the same subdir only asks for rv64imafdc + zfhmin, so wouldn't a host with zfhmin and no zbs get labeled rva22u64 and receive binaries it can't run?
Might be worth a test that every extension in a subdir's -march is required by its spec line; the vendored targets already pass that, it's only the profile entries.
| if [ "${machine_type}" == "riscv64" ]; then | ||
| # RISC-V ISA strings use '_' as extension separators. | ||
| # Convert them to space-separated feature tokens so they | ||
| # can be matched like x86 CPU flags. | ||
| cpu_flags=${cpu_flags//_/ } | ||
| fi |
There was a problem hiding this comment.
While the format is open: the leading rv64imafdch blob still matches as one token, so a superset host (say rv64imafdcvh) fails a spec asking rv64imafdch.
Splitting the blob into per-letter tokens would make the base extensions individually matchable, which I think is what the description is going for?
If the expansion happens on both sides at match time, the spec files keep the readable concatenated form, something like
riscv_expand_base(){
# rv64imafdch -> rv64 i m a f d c h ; every other token passes through
local out="" tok
for tok in "$@"; do
if [ "${tok#rv64}" != "${tok}" ]; then
out="${out} rv64 $(printf '%s' "${tok#rv64}" | sed 's/./& /g')"
else
out="${out} ${tok}"
fi
done
printf '%s' "${out}"
}
# here, for the host flags:
cpu_flags=$(riscv_expand_base ${cpu_flags})
# and at the match, for the spec side:
check_allinfirst "${cpu_flags[*]}" $(riscv_expand_base ${arch_spec[2]})so a superset host matches any subset spec without the spec files caring how the kernel concatenated the letters.
Also trailing whitespace on this line, and the comment block is tab-indented against the file.
| end | ||
| map_fh:close() | ||
| if optarch == nil or optarch == "" then | ||
| LmodError("No RISC-V EASYBUILD_OPTARCH mapping for '" .. eessi_software_subdir .. "' (add it to " .. map_file .. ")") |
There was a problem hiding this comment.
This makes a missing map line fail module load for everyone on that target, including site installs with their own optarch policy.
Maybe warn and fall through to the default like the CUDA capabilities do?
| echo "MODULEPATH=${MODULEPATH}" | ||
|
|
||
| # foss/2025b → GCC/14.3.0; lfoss/2025b → llvm-compilers/20.1.8 | ||
| module load GCC/14.3.0 |
There was a problem hiding this comment.
Two things maybe worth a comment in the file: the module versions are pinned, so the next dev.eessi.io rebuild breaks this, and -fsyntax-only never reaches the assembler, so the job name overclaims a bit.
The triple override combo also skips the init scripts' own consistency checks; one line saying it's deliberate would help the next reader.
| "riscv64/sifive/p550" "0x489" "rv64imafdch_zicsr_zifencei_zba_zbb_sscofpmf" # HiFive Premier P550 | ||
| "riscv64/spacemit/x60" "0x710" "rv64imafdcv_sscofpmf_sstc_svpbmt_zicbom_zicboz_zicbop_zihintpause" # Banana Pi F3 | ||
| "riscv64/spacemit/x60-k6.6" "0x710" "rv64imafdcv_zicbom_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zfhmin_zca_zcd_zba_zbb_zbc_zbs_zkt_zve32f_zve32x_zve64d_zve64f_zve64x_zvfh_zvfhmin_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt" # Banana Pi F3 k6.6 | ||
| "riscv64/generic/rva20u64" "" "rv64imafdc" |
There was a problem hiding this comment.
I don't think these three can ever match?
cpupath() compares vendors first 1 and only aarch64 has an unknown-vendor fallback 2, so everything real gets an mvendorid 3 and an empty vendor field never survives the gate.
I think the riscv64/sifive/u74-mc:riscv64/generic in the u74-mc fixture 5 is also shown that way because of this.
Its isa line 4 tokenizes to
rv64imafdc zicntr zicsr zifencei zihpm zca zcd zba zbb
which contains everything the rva20u64 entry asks for (rv64imafdc, 6), so feature-wise it should have matched.
Separately, the entry is a lot weaker than the actual RVA20U64 profile, which also mandates Zicsr, Zicntr, and Zifencei among others 7; probably fine as a matching heuristic, but worth saying which one it's meant to be.
Maybe add a riscv fallback like the ARM 0x41 one 2, or drop the three entries until that exists?
There's also no generic/rva* fixture right now, so CI can't catch this.
This update will treat the isa line from a riscv cpu (e.g. rv64imafdcv_zicbom_zicboz_zicntr_zicond_zicsr_zifencei_zihintpause_zihpm_zfh_zfhmin_zca_zcd_zba_zbb_zbc_zbs_zkt_zve32f_zve32x_zve64d_zve64f_zve64x_zvfh_zvfhmin_zvkt_sscofpmf_sstc_svinval_svnapot_svpbmt) and sed it into seperate flags which can be matched as relevant (!) features, the same way we do for x86_64. This should allow cpu's with the same relevant features, but from different vendors to be matched to the same cpu paths.