From 40f5e78184085c9df5a73ec9e79807f1ac6a04fa Mon Sep 17 00:00:00 2001 From: TechnoPorg Date: Mon, 2 Nov 2026 11:10:03 -0500 Subject: [PATCH 1/2] feat: generate RISC-V intrinsic specs using C headers --- .github/workflows/main.yml | 5 +- Cargo.lock | 26 +- crates/stdarch-gen-riscv/Cargo.toml | 11 + crates/stdarch-gen-riscv/bitmanip.spec | 172 +++++++++++++ crates/stdarch-gen-riscv/crypto.spec | 178 +++++++++++++ crates/stdarch-gen-riscv/riscv_bitmanip.h | 288 +++++++++++++++++++++ crates/stdarch-gen-riscv/riscv_crypto.h | 299 ++++++++++++++++++++++ crates/stdarch-gen-riscv/src/generate.rs | 165 ++++++++++++ crates/stdarch-gen-riscv/src/main.rs | 42 +++ 9 files changed, 1177 insertions(+), 9 deletions(-) create mode 100644 crates/stdarch-gen-riscv/Cargo.toml create mode 100644 crates/stdarch-gen-riscv/bitmanip.spec create mode 100644 crates/stdarch-gen-riscv/crypto.spec create mode 100644 crates/stdarch-gen-riscv/riscv_bitmanip.h create mode 100644 crates/stdarch-gen-riscv/riscv_crypto.h create mode 100644 crates/stdarch-gen-riscv/src/generate.rs create mode 100644 crates/stdarch-gen-riscv/src/main.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a27f137d10..af767fc3b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -322,7 +322,7 @@ jobs: # Check that the generated files agree with the checked-in versions. check-stdarch-gen: needs: [style] - name: Check stdarch-gen-{arm, loongarch, hexagon} output + name: Check stdarch-gen-{arm, loongarch, hexagon, riscv} output runs-on: ubuntu-latest env: STDARCH_GEN_MODE: check @@ -342,6 +342,9 @@ jobs: - name: Check hexagon run: | cargo run -p stdarch-gen-hexagon --release + - name: Check riscv + run: | + cargo run -p stdarch-gen-riscv --release # Run some tests with Miri. Most stdarch functions use platform-specific intrinsics # that Miri does not support. Also Miri is reltively slow. diff --git a/Cargo.lock b/Cargo.lock index 5750be2086..092c2616b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "assert-instr-macro" @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc-demangle" @@ -861,6 +861,16 @@ dependencies = [ "stdarch-gen-common", ] +[[package]] +name = "stdarch-gen-riscv" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "regex", + "stdarch-gen-common", +] + [[package]] name = "stdarch-test" version = "0.1.0" diff --git a/crates/stdarch-gen-riscv/Cargo.toml b/crates/stdarch-gen-riscv/Cargo.toml new file mode 100644 index 0000000000..5656a7be1e --- /dev/null +++ b/crates/stdarch-gen-riscv/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "stdarch-gen-riscv" +version = "0.1.0" +authors = ["The Rust Project Developers"] +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +clap = { version = "4", features = ["derive", "env"] } +regex = "1.10" +stdarch-gen-common = { path = "../stdarch-gen-common" } diff --git a/crates/stdarch-gen-riscv/bitmanip.spec b/crates/stdarch-gen-riscv/bitmanip.spec new file mode 100644 index 0000000000..0a5e5e292d --- /dev/null +++ b/crates/stdarch-gen-riscv/bitmanip.spec @@ -0,0 +1,172 @@ +// This code is automatically generated. DO NOT MODIFY. +// ``` +// cargo run -p stdarch-gen-riscv +// ``` + +name = clz_32 +arch = both +return_type = unsigned +arguments = uint32_t x +extensions = zbb + +name = ctz_32 +arch = both +return_type = unsigned +arguments = uint32_t x +extensions = zbb + +name = cpop_32 +arch = both +return_type = unsigned +arguments = uint32_t x +extensions = zbb + +name = orc_b_32 +arch = both +return_type = uint32_t +arguments = uint32_t x +extensions = zbb + +name = clz_64 +arch = rv64 +return_type = unsigned +arguments = uint64_t x +extensions = zbb + +name = ctz_64 +arch = rv64 +return_type = unsigned +arguments = uint64_t x +extensions = zbb + +name = cpop_64 +arch = rv64 +return_type = unsigned +arguments = uint64_t x +extensions = zbb + +name = orc_b_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t x +extensions = zbb + +name = ror_32 +arch = both +return_type = uint32_t +arguments = uint32_t x, uint32_t shamt +extensions = zbb, zbkb + +name = rol_32 +arch = both +return_type = uint32_t +arguments = uint32_t x, uint32_t shamt +extensions = zbb, zbkb + +name = rev8_32 +arch = both +return_type = uint32_t +arguments = uint32_t x +extensions = zbb, zbkb + +name = ror_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t x, uint32_t shamt +extensions = zbb, zbkb + +name = rol_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t x, uint32_t shamt +extensions = zbb, zbkb + +name = rev8_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t x +extensions = zbb, zbkb + +name = brev8_32 +arch = both +return_type = uint32_t +arguments = uint32_t x +extensions = zbkb + +name = zip_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t x +extensions = zbkb + +name = unzip_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t x +extensions = zbkb + +name = brev8_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t x +extensions = zbkb + +name = clmul_32 +arch = both +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zbc, zbkc + +name = clmulh_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zbc, zbkc + +name = clmul_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zbc, zbkc + +name = clmulh_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zbc, zbkc + +name = clmulr_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zbc + +name = clmulr_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zbc + +name = xperm4_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zbkx + +name = xperm8_32 +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zbkx + +name = xperm4_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zbkx + +name = xperm8_64 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zbkx diff --git a/crates/stdarch-gen-riscv/crypto.spec b/crates/stdarch-gen-riscv/crypto.spec new file mode 100644 index 0000000000..f9cf9e8839 --- /dev/null +++ b/crates/stdarch-gen-riscv/crypto.spec @@ -0,0 +1,178 @@ +// This code is automatically generated. DO NOT MODIFY. +// ``` +// cargo run -p stdarch-gen-riscv +// ``` + +name = aes32dsi +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zknd + +name = aes32dsmi +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zknd + +name = aes64ds +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zknd + +name = aes64dsm +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zknd + +name = aes64im +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1 +extensions = zknd + +name = aes64ks1i +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, const int rnum +extensions = zknd, zkne + +name = aes64ks2 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zknd, zkne + +name = aes32esi +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zkne + +name = aes32esmi +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zkne + +name = aes64es +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zkne + +name = aes64esm +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1, uint64_t rs2 +extensions = zkne + +name = sha256sig0 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zknh + +name = sha256sig1 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zknh + +name = sha256sum0 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zknh + +name = sha256sum1 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zknh + +name = sha512sig0h +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sig0l +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sig1h +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sig1l +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sum0r +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sum1r +arch = rv32 +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2 +extensions = zknh + +name = sha512sig0 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1 +extensions = zknh + +name = sha512sig1 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1 +extensions = zknh + +name = sha512sum0 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1 +extensions = zknh + +name = sha512sum1 +arch = rv64 +return_type = uint64_t +arguments = uint64_t rs1 +extensions = zknh + +name = sm3p0 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zksh + +name = sm3p1 +arch = both +return_type = uint32_t +arguments = uint32_t rs1 +extensions = zksh + +name = sm4ed +arch = both +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zksed + +name = sm4ks +arch = both +return_type = uint32_t +arguments = uint32_t rs1, uint32_t rs2, const int bs +extensions = zksed diff --git a/crates/stdarch-gen-riscv/riscv_bitmanip.h b/crates/stdarch-gen-riscv/riscv_bitmanip.h new file mode 100644 index 0000000000..d9e46b8db8 --- /dev/null +++ b/crates/stdarch-gen-riscv/riscv_bitmanip.h @@ -0,0 +1,288 @@ +/* RISC-V Bitmanip Extension intrinsics include file. + Copyright (C) 2024-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. + + Under Section 7 of GPL version 3, you are granted additional + permissions described in the GCC Runtime Library Exception, version + 3.1, as published by the Free Software Foundation. + + You should have received a copy of the GNU General Public License and + a copy of the GCC Runtime Library Exception along with this program; + see the files COPYING3 and COPYING.RUNTIME respectively. If not, see + . */ + +#ifndef __RISCV_BITMANIP_H +#define __RISCV_BITMANIP_H + +#include + +#define __riscv_intrinsic_zbb 1 +#define __riscv_intrinsic_zbc 1 +#define __riscv_intrinsic_zbkb 1 +#define __riscv_intrinsic_zbkc 1 +#define __riscv_intrinsic_zbkx 1 + +#if !defined(__riscv_intrinsic_zkn) && defined(__riscv_intrinsic_zbkb) && \ + defined(__riscv_intrinsic_zbkc) && defined(__riscv_intrinsic_zbkx) && \ + defined(__riscv_intrinsic_zknd) && defined(__riscv_intrinsic_zkne) && \ + defined(__riscv_intrinsic_zknh) +#define __riscv_intrinsic_zkn 1 +#endif + +#if !defined(__riscv_intrinsic_zks) && defined(__riscv_intrinsic_zbkb) && \ + defined(__riscv_intrinsic_zbkc) && defined(__riscv_intrinsic_zbkx) && \ + defined(__riscv_intrinsic_zksed) && defined(__riscv_intrinsic_zksh) +#define __riscv_intrinsic_zks 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__riscv_zbb) + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_clz_32(uint32_t x) { + return __builtin_riscv_clz_32(x); +} + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_ctz_32(uint32_t x) { + return __builtin_riscv_ctz_32(x); +} + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_cpop_32(uint32_t x) { + return __builtin_riscv_popcount_32(x); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_orc_b_32(uint32_t x) { + return __builtin_riscv_orc_b_32(x); +} + +#if __riscv_xlen == 64 + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_clz_64(uint64_t x) { + return __builtin_riscv_clz_64(x); +} + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_ctz_64(uint64_t x) { + return __builtin_riscv_ctz_64(x); +} + +extern __inline unsigned + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_cpop_64(uint64_t x) { + return __builtin_riscv_popcount_64(x); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_orc_b_64(uint64_t x) { + return __builtin_riscv_orc_b_64(x); +} + +#endif + +#endif // __riscv_zbb + +#if defined(__riscv_zbb) || defined(__riscv_zbkb) + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_ror_32(uint32_t x, uint32_t shamt) { + return __builtin_riscv_ror_32(x, shamt); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_rol_32(uint32_t x, uint32_t shamt) { + return __builtin_riscv_rol_32(x, shamt); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_rev8_32(uint32_t x) { + return __builtin_bswap32(x); +} + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_ror_64(uint64_t x, uint32_t shamt) { + return __builtin_riscv_ror_64(x, shamt); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_rol_64(uint64_t x, uint32_t shamt) { + return __builtin_riscv_rol_64(x, shamt); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_rev8_64(uint64_t x) { + return __builtin_bswap64(x); +} + +#endif + +#endif // __riscv_zbb || __riscv_zbkb + +#if defined(__riscv_zbkb) + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_brev8_32(uint32_t x) { + return __builtin_riscv_brev8_32(x); +} + +#if __riscv_xlen == 32 + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_zip_32(uint32_t x) { + return __builtin_riscv_zip(x); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_unzip_32(uint32_t x) { + return __builtin_riscv_unzip(x); +} + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_brev8_64(uint64_t x) { + return __builtin_riscv_brev8_64(x); +} + +#endif + +#endif // __riscv_zbkb + +#if defined(__riscv_zbc) || defined(__riscv_zbkc) + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmul_32(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_clmul_32(rs1, rs2); +} + +#if __riscv_xlen == 32 + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmulh_32(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_clmulh_32(rs1, rs2); +} + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmul_64(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_clmul_64(rs1, rs2); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmulh_64(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_clmulh_64(rs1, rs2); +} + +#endif + +#endif // __riscv_zbc || __riscv_zbkc + +#if defined(__riscv_zbc) + +#if __riscv_xlen == 32 + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmulr_32(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_clmulr_32(rs1, rs2); +} + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_clmulr_64(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_clmulr_64(rs1, rs2); +} + +#endif + +#endif // __riscv_zbc + +#if defined(__riscv_zbkx) + +#if __riscv_xlen == 32 + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_xperm4_32(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_xperm4(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_xperm8_32(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_xperm8(rs1, rs2); +} + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_xperm4_64(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_xperm4(rs1, rs2); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_xperm8_64(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_xperm8(rs1, rs2); +} + +#endif + +#endif // __riscv_zbkx + +#if defined(__cplusplus) +} +#endif // __cplusplus +#endif // __RISCV_BITMANIP_H diff --git a/crates/stdarch-gen-riscv/riscv_crypto.h b/crates/stdarch-gen-riscv/riscv_crypto.h new file mode 100644 index 0000000000..5f724ced12 --- /dev/null +++ b/crates/stdarch-gen-riscv/riscv_crypto.h @@ -0,0 +1,299 @@ +/* RISC-V 'Scalar Crypto' Extension intrinsics include file. + Copyright (C) 2024-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. + + Under Section 7 of GPL version 3, you are granted additional + permissions described in the GCC Runtime Library Exception, version + 3.1, as published by the Free Software Foundation. + + You should have received a copy of the GNU General Public License and + a copy of the GCC Runtime Library Exception along with this program; + see the files COPYING3 and COPYING.RUNTIME respectively. If not, see + . */ + +#ifndef __RISCV_SCALAR_CRYPTO_H +#define __RISCV_SCALAR_CRYPTO_H + +#include + +#define __riscv_intrinsic_zknd 1 +#define __riscv_intrinsic_zkne 1 +#define __riscv_intrinsic_zknh 1 +#define __riscv_intrinsic_zksed 1 +#define __riscv_intrinsic_zksh 1 + +#if !defined(__riscv_intrinsic_zkn) && defined(__riscv_intrinsic_zbkb) && \ + defined(__riscv_intrinsic_zbkc) && defined(__riscv_intrinsic_zbkx) && \ + defined(__riscv_intrinsic_zknd) && defined(__riscv_intrinsic_zkne) && \ + defined(__riscv_intrinsic_zknh) +#define __riscv_intrinsic_zkn 1 +#endif + +#if !defined(__riscv_intrinsic_zks) && defined(__riscv_intrinsic_zbkb) && \ + defined(__riscv_intrinsic_zbkc) && defined(__riscv_intrinsic_zbkx) && \ + defined(__riscv_intrinsic_zksed) && defined(__riscv_intrinsic_zksh) +#define __riscv_intrinsic_zks 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__riscv_zknd) + +#if __riscv_xlen == 32 + +#ifdef __OPTIMIZE__ + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes32dsi(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_aes32dsi(rs1, rs2, bs); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes32dsmi(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_aes32dsmi(rs1, rs2, bs); +} + +#else +#define __riscv_aes32dsi(x, y, bs) __builtin_riscv_aes32dsi(x, y, bs) +#define __riscv_aes32dsmi(x, y, bs) __builtin_riscv_aes32dsmi(x, y, bs) +#endif + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64ds(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_aes64ds(rs1, rs2); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64dsm(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_aes64dsm(rs1, rs2); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64im(uint64_t rs1) { + return __builtin_riscv_aes64im(rs1); +} +#endif +#endif // __riscv_zknd + +#if (defined(__riscv_zknd) || defined(__riscv_zkne)) && (__riscv_xlen == 64) + +#ifdef __OPTIMIZE__ + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64ks1i(uint64_t rs1, const int rnum) { + return __builtin_riscv_aes64ks1i(rs1, rnum); +} + +#else +#define __riscv_aes64ks1i(x, rnum) __builtin_riscv_aes64ks1i(x, rnum) +#endif + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64ks2(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_aes64ks2(rs1, rs2); +} + +#endif // __riscv_zknd || __riscv_zkne + +#if defined(__riscv_zkne) + +#if __riscv_xlen == 32 + +#ifdef __OPTIMIZE__ + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes32esi(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_aes32esi(rs1, rs2, bs); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes32esmi(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_aes32esmi(rs1, rs2, bs); +} + +#else +#define __riscv_aes32esi(x, y, bs) __builtin_riscv_aes32esi(x, y, bs) +#define __riscv_aes32esmi(x, y, bs) __builtin_riscv_aes32esmi(x, y, bs) +#endif + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64es(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_aes64es(rs1, rs2); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_aes64esm(uint64_t rs1, uint64_t rs2) { + return __builtin_riscv_aes64esm(rs1, rs2); +} +#endif +#endif // __riscv_zkne + +#if defined(__riscv_zknh) + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_sha256sig0(uint32_t rs1) { + return __builtin_riscv_sha256sig0(rs1); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha256sig1(uint32_t rs1) { + return __builtin_riscv_sha256sig1(rs1); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha256sum0(uint32_t rs1) { + return __builtin_riscv_sha256sum0(rs1); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha256sum1(uint32_t rs1) { + return __builtin_riscv_sha256sum1(rs1); +} + +#if __riscv_xlen == 32 + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sig0h(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sig0h(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sig0l(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sig0l(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sig1h(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sig1h(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sig1l(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sig1l(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sum0r(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sum0r(rs1, rs2); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sum1r(uint32_t rs1, uint32_t rs2) { + return __builtin_riscv_sha512sum1r(rs1, rs2); +} + +#endif + +#if __riscv_xlen == 64 + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_sha512sig0(uint64_t rs1) { + return __builtin_riscv_sha512sig0(rs1); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sig1(uint64_t rs1) { + return __builtin_riscv_sha512sig1(rs1); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sum0(uint64_t rs1) { + return __builtin_riscv_sha512sum0(rs1); +} + +extern __inline uint64_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sha512sum1(uint64_t rs1) { + return __builtin_riscv_sha512sum1(rs1); +} +#endif +#endif // __riscv_zknh + +#if defined(__riscv_zksh) + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, + __artificial__)) __riscv_sm3p0(uint32_t rs1) { + return __builtin_riscv_sm3p0(rs1); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sm3p1(uint32_t rs1) { + return __builtin_riscv_sm3p1(rs1); +} + +#endif // __riscv_zksh + +#if defined(__riscv_zksed) + +#ifdef __OPTIMIZE__ + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sm4ed(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_sm4ed(rs1, rs2, bs); +} + +extern __inline uint32_t + __attribute__((__gnu_inline__, __always_inline__, __artificial__)) + __riscv_sm4ks(uint32_t rs1, uint32_t rs2, const int bs) { + return __builtin_riscv_sm4ks(rs1, rs2, bs); +} + +#else +#define __riscv_sm4ed(x, y, bs) __builtin_riscv_sm4ed(x, y, bs); +#define __riscv_sm4ks(x, y, bs) __builtin_riscv_sm4ks(x, y, bs); +#endif + +#endif // __riscv_zksed + +#if defined(__cplusplus) +} +#endif // __cplusplus +#endif // __RISCV_SCALAR_CRYPTO_H diff --git a/crates/stdarch-gen-riscv/src/generate.rs b/crates/stdarch-gen-riscv/src/generate.rs new file mode 100644 index 0000000000..59e39d3bca --- /dev/null +++ b/crates/stdarch-gen-riscv/src/generate.rs @@ -0,0 +1,165 @@ +use std::{fmt, fs, path::Path}; + +use regex::regex; + +pub fn generate(crate_dir: &Path, out_dir: &Path, header: &str) -> anyhow::Result<()> { + let in_file = crate_dir.join(format!("riscv_{header}.h")); + let contents = fs::read_to_string(in_file)?; + let intrinsic_prototypes = parse_header(contents); + let output = generate_spec_file(intrinsic_prototypes); + let out_file = out_dir.join(format!("{header}.spec")); + fs::write(out_file, output)?; + Ok(()) +} + +#[derive(PartialEq, Copy, Clone)] +pub enum RiscvArch { + Both, + Rv32, + Rv64, +} + +impl fmt::Display for RiscvArch { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RiscvArch::Both => f.write_str("both"), + RiscvArch::Rv32 => f.write_str("rv32"), + RiscvArch::Rv64 => f.write_str("rv64"), + } + } +} + +pub struct RiscvIntrinsic { + pub name: String, + pub arch: RiscvArch, + pub return_type: String, + pub arguments: String, + pub extensions: Vec, +} + +fn parse_header(contents: String) -> Vec { + let mut intrinsics = vec![]; + + let mut lines = contents.lines().map(|line| line.trim()); + let mut current_extensions = None; + let mut current_arch = RiscvArch::Both; + // Some #if statements have the xlen condition in the same line as the extension condition, + // which complicates parsing + let mut xlen_if_nesting = false; + while let Some(mut line) = lines.next() { + // Parse extensions: start of block + let line_extensions: Vec<_> = regex!(r"defined\(__riscv_(\w+)\)") + .captures_iter(line) + .map(|captures| captures[1].to_string()) + .collect(); + if !line_extensions.is_empty() && (line.contains("\\") || !line.starts_with("#if ")) { + // These are the "meta" extension definitions at the start of the file + continue; + } + if current_extensions.is_some() && !line_extensions.is_empty() { + panic!( + "parser error: should not have nested extensions.\nLine:{line}\nActive extensions:\n{current_extensions:?}" + ); + } else if !line_extensions.is_empty() { + current_extensions = Some(line_extensions); + } + + // Parse extensions: end of block + let Some(extensions) = ¤t_extensions else { + continue; + }; + let end_if_comment = extensions + .iter() + .map(|extension| format!("__riscv_{extension}")) + .reduce(|a, b| format!("{a} || {b}")) + .unwrap(); + let end_line = format!("#endif // {end_if_comment}"); + if line == end_line { + current_extensions = None; + current_arch = RiscvArch::Both; + continue; + } + + // Parse bit width + if line.contains("__riscv_xlen == 32") { + assert!( + current_arch == RiscvArch::Both, + "parser error: should not have nested xlen\nLine:\n{line}" + ); + current_arch = RiscvArch::Rv32; + xlen_if_nesting = line == "#if __riscv_xlen == 32"; + } + if line.contains("__riscv_xlen == 64") { + assert!( + current_arch == RiscvArch::Both, + "parser error: should not have nested xlen\nLine:\n{line}" + ); + current_arch = RiscvArch::Rv64; + xlen_if_nesting = line == "#if __riscv_xlen == 64"; + } + if current_arch != RiscvArch::Both && line == "#endif" && xlen_if_nesting { + current_arch = RiscvArch::Both; + } + + // Parse function declaration + let Some(return_type) = regex!("extern __inline (.*)") + .captures(line) + .map(|captures| captures[1].to_string()) + else { + continue; + }; + + let (name, arguments) = loop { + let Some(captures) = regex!(r"__riscv_(\w+)\((.*?)\)").captures(line) else { + line = lines.next().unwrap(); + continue; + }; + let name = captures[1].to_string(); + let arguments = captures[2].to_string(); + + break (name, arguments); + }; + + // Skip to the end of this function + while line != "}" { + line = lines.next().unwrap(); + } + + intrinsics.push(RiscvIntrinsic { + name, + arch: current_arch, + return_type, + arguments, + extensions: extensions.clone(), + }); + } + intrinsics +} + +fn generate_spec_file(intrinsics: Vec) -> String { + let mut output = r#"// This code is automatically generated. DO NOT MODIFY. +// ``` +// cargo run -p stdarch-gen-riscv +// ``` +"# + .to_string(); + + output.extend(intrinsics.into_iter().map(|intrinsic| { + format!( + " +name = {} +arch = {} +return_type = {} +arguments = {} +extensions = {} +", + intrinsic.name, + intrinsic.arch, + intrinsic.return_type, + intrinsic.arguments, + intrinsic.extensions.join(", ") + ) + })); + + output +} diff --git a/crates/stdarch-gen-riscv/src/main.rs b/crates/stdarch-gen-riscv/src/main.rs new file mode 100644 index 0000000000..17fad5c8df --- /dev/null +++ b/crates/stdarch-gen-riscv/src/main.rs @@ -0,0 +1,42 @@ +use clap::Parser; +use std::path::PathBuf; +use stdarch_gen_common::{GeneratorCtx, Mode, run_generator}; + +mod generate; + +/// RISC-V intrinsic spec generator. +/// +/// Produces the *.spec files in this directory by parsing the C headers in which they are defined. +/// +/// Run in check or bless mode via `STDARCH_GEN_MODE`. +#[derive(clap::Parser)] +struct Args { + /// Generation mode. + #[arg(long, env = "STDARCH_GEN_MODE")] + mode: Option, + /// Path to a rustfmt binary that will be used to reformat the generated code. + /// If unset, it will just use "rustfmt" from the environment. + #[arg(long)] + rustfmt_path: Option, +} + +fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let crate_dir = std::env::var("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| std::env::current_dir().unwrap()); + + // Either "check" to check the output versus the committed output, or "bless" + // to update the output. + let mode = args.mode.unwrap_or_default(); + let ctx = GeneratorCtx::new(args.rustfmt_path); + + run_generator(&ctx, &crate_dir, mode, |out_dir| -> anyhow::Result<()> { + generate::generate(&crate_dir, out_dir, "bitmanip")?; + generate::generate(&crate_dir, out_dir, "crypto")?; + Ok(()) + })?; + + Ok(()) +} From e95e3de83a75c89c87452813137350e53afc6a69 Mon Sep 17 00:00:00 2001 From: TechnoPorg Date: Wed, 28 Oct 2026 11:55:31 -0400 Subject: [PATCH 2/2] feat: fuzz testing of RISC-V intrinsics --- .github/workflows/main.yml | 4 + .../riscv32gc-unknown-linux-gnu/Dockerfile | 7 ++ .../riscv64gc-unknown-linux-gnu/Dockerfile | 9 +- ci/intrinsic-test.sh | 20 +++ .../intrinsic-test/missing_riscv_common.txt | 14 +++ crates/intrinsic-test/src/arm/json_parser.rs | 2 +- crates/intrinsic-test/src/arm/mod.rs | 4 +- crates/intrinsic-test/src/common/intrinsic.rs | 4 +- crates/intrinsic-test/src/loongarch/mod.rs | 2 +- crates/intrinsic-test/src/loongarch/parser.rs | 2 +- crates/intrinsic-test/src/main.rs | 4 + crates/intrinsic-test/src/riscv/intrinsic.rs | 19 +++ crates/intrinsic-test/src/riscv/mod.rs | 104 ++++++++++++++++ crates/intrinsic-test/src/riscv/parser.rs | 117 ++++++++++++++++++ crates/intrinsic-test/src/riscv/types.rs | 72 +++++++++++ crates/intrinsic-test/src/x86/xml_parser.rs | 2 +- 16 files changed, 377 insertions(+), 9 deletions(-) create mode 100644 crates/intrinsic-test/missing_riscv_common.txt create mode 100644 crates/intrinsic-test/src/riscv/intrinsic.rs create mode 100644 crates/intrinsic-test/src/riscv/mod.rs create mode 100644 crates/intrinsic-test/src/riscv/parser.rs create mode 100644 crates/intrinsic-test/src/riscv/types.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af767fc3b3..3d0f6f6f9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -283,12 +283,16 @@ jobs: - aarch64_be-unknown-linux-gnu - armv7-unknown-linux-gnueabihf - loongarch64-unknown-linux-gnu + - riscv64gc-unknown-linux-gnu + - riscv32gc-unknown-linux-gnu - x86_64-unknown-linux-gnu profile: [dev, release] cc: [clang, gcc] include: - target: aarch64_be-unknown-linux-gnu build_std: true + - target: riscv32gc-unknown-linux-gnu + build_std: true - target: x86_64-unknown-linux-gnu cc: icx profile: dev diff --git a/ci/docker/riscv32gc-unknown-linux-gnu/Dockerfile b/ci/docker/riscv32gc-unknown-linux-gnu/Dockerfile index fb1718b338..3e4c2f5fa2 100644 --- a/ci/docker/riscv32gc-unknown-linux-gnu/Dockerfile +++ b/ci/docker/riscv32gc-unknown-linux-gnu/Dockerfile @@ -10,6 +10,13 @@ RUN wget "https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/ -O riscv-toolchain.tar.xz RUN tar -xJf riscv-toolchain.tar.xz +RUN wget https://ci-mirrors.rust-lang.org/llvm/llvm-22.1.4-x86_64.tar.gz -O llvm.tar.xz +RUN mkdir llvm +RUN tar -xvf llvm.tar.xz --strip-components=1 -C llvm + +ENV CLANG_PATH="/llvm/bin/clang" +ENV GCC_PATH=/riscv/bin/riscv32-unknown-linux-gnu-gcc + ENV CARGO_TARGET_RISCV32GC_UNKNOWN_LINUX_GNU_LINKER=/riscv/bin/riscv32-unknown-linux-gnu-gcc \ CARGO_TARGET_RISCV32GC_UNKNOWN_LINUX_GNU_RUNNER="qemu-riscv32 -cpu max -L /riscv/sysroot" \ OBJDUMP=llvm-objdump diff --git a/ci/docker/riscv64gc-unknown-linux-gnu/Dockerfile b/ci/docker/riscv64gc-unknown-linux-gnu/Dockerfile index 10316cae6c..d755b8abd5 100644 --- a/ci/docker/riscv64gc-unknown-linux-gnu/Dockerfile +++ b/ci/docker/riscv64gc-unknown-linux-gnu/Dockerfile @@ -3,7 +3,14 @@ FROM ubuntu:25.10 RUN apt-get update && apt-get install -y --no-install-recommends \ gcc libc6-dev qemu-user ca-certificates \ gcc-riscv64-linux-gnu libc6-dev-riscv64-cross \ - llvm + llvm wget + +RUN wget https://ci-mirrors.rust-lang.org/llvm/llvm-22.1.4-x86_64.tar.gz -O llvm.tar.xz +RUN mkdir llvm +RUN tar -xvf llvm.tar.xz --strip-components=1 -C llvm + +ENV CLANG_PATH="/llvm/bin/clang" +ENV GCC_PATH=riscv64-linux-gnu-gcc ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc \ CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_RUNNER="qemu-riscv64 -cpu max -L /usr/riscv64-linux-gnu" \ diff --git a/ci/intrinsic-test.sh b/ci/intrinsic-test.sh index 3a926c3b9f..ae3e045d6e 100755 --- a/ci/intrinsic-test.sh +++ b/ci/intrinsic-test.sh @@ -71,6 +71,19 @@ case ${TARGET} in ARCH=loongarch64 RUNTIME_RUSTFLAGS=-Ctarget-feature=+lsx,+lasx,+frecipe ;; + + riscv64*) + export CFLAGS="-I/usr/riscv64-linux-gnu/include/" + ARCH=riscv64 + RUNTIME_RUSTFLAGS= + ;; + + riscv32*) + export CFLAGS="-I/riscv/sysroot/usr/include --sysroot=/riscv/sysroot" + ARCH=riscv32 + RUNTIME_RUSTFLAGS= + ;; + *) ;; @@ -94,6 +107,13 @@ case "${TARGET}" in --target "${TARGET}" \ --cc-arg-style "${CC_ARG_STYLE}" ;; + riscv*) + cargo run "${INTRINSIC_TEST}" --release \ + --bin intrinsic-test -- crates/stdarch-gen-riscv \ + --skip "crates/intrinsic-test/missing_riscv_common.txt" \ + --target "${TARGET}" \ + --cc-arg-style "${CC_ARG_STYLE}" + ;; *) cargo run "${INTRINSIC_TEST}" --release \ --bin intrinsic-test -- intrinsics_data/arm_intrinsics.json \ diff --git a/crates/intrinsic-test/missing_riscv_common.txt b/crates/intrinsic-test/missing_riscv_common.txt new file mode 100644 index 0000000000..5c2b747e08 --- /dev/null +++ b/crates/intrinsic-test/missing_riscv_common.txt @@ -0,0 +1,14 @@ +clz_32 +clz_64 +ctz_32 +ctz_64 +cpop_32 +cpop_64 +ror_32 +ror_64 +rol_32 +rol_64 +rev8_32 +rev8_64 +brev8_32 +brev8_64 diff --git a/crates/intrinsic-test/src/arm/json_parser.rs b/crates/intrinsic-test/src/arm/json_parser.rs index 013c20f2db..de0ad2588d 100644 --- a/crates/intrinsic-test/src/arm/json_parser.rs +++ b/crates/intrinsic-test/src/arm/json_parser.rs @@ -140,7 +140,7 @@ fn json_to_intrinsic( arguments, results: result_ty, arch_tags: intr.architectures, - extension: intr.simd_isa, + extensions: vec![intr.simd_isa], }) } diff --git a/crates/intrinsic-test/src/arm/mod.rs b/crates/intrinsic-test/src/arm/mod.rs index 38f57310a3..b3ce6985bd 100644 --- a/crates/intrinsic-test/src/arm/mod.rs +++ b/crates/intrinsic-test/src/arm/mod.rs @@ -125,12 +125,12 @@ impl SupportedArchitecture for Arm { // Skip A64-specific intrinsics on A32 .filter(|i| !(a32 && i.arch_tags == vec!["A64".to_string()])) // Skip SVE intrinsics on big endian - .filter(|i| !(big_endian && (i.extension == "SVE" || i.extension == "SVE2"))) + .filter(|i| !(big_endian && (i.extensions[0] == "SVE" || i.extensions[0] == "SVE2"))) // Skip SVE intrinsics when testing against GCC as our wrappers run into ICEs // See .filter(|i| { !(matches!(cli_options.cc_arg_style, CcArgStyle::Gcc) - && (i.extension == "SVE" || i.extension == "SVE2")) + && (i.extensions[0] == "SVE" || i.extensions[0] == "SVE2")) }) .take(sample_size) .collect::>(); diff --git a/crates/intrinsic-test/src/common/intrinsic.rs b/crates/intrinsic-test/src/common/intrinsic.rs index d5d903d941..3a54f16eca 100644 --- a/crates/intrinsic-test/src/common/intrinsic.rs +++ b/crates/intrinsic-test/src/common/intrinsic.rs @@ -17,8 +17,8 @@ pub struct Intrinsic { /// Any architecture-specific tags. pub arch_tags: Vec, - /// Specific extension that the intrinsic is from - pub extension: String, + /// Specific extension(s) that the intrinsic is from + pub extensions: Vec, } impl Intrinsic { diff --git a/crates/intrinsic-test/src/loongarch/mod.rs b/crates/intrinsic-test/src/loongarch/mod.rs index 00cff20a0f..8f7cce7ac9 100644 --- a/crates/intrinsic-test/src/loongarch/mod.rs +++ b/crates/intrinsic-test/src/loongarch/mod.rs @@ -42,7 +42,7 @@ impl SupportedArchitecture for LoongArch { if self .intrinsics .iter() - .any(|intrinsic| intrinsic.extension == "LASX") + .any(|intrinsic| intrinsic.extensions[0] == "LASX") { flags.push("-mlasx"); } diff --git a/crates/intrinsic-test/src/loongarch/parser.rs b/crates/intrinsic-test/src/loongarch/parser.rs index ecb5d1ab3e..c381bbfb96 100644 --- a/crates/intrinsic-test/src/loongarch/parser.rs +++ b/crates/intrinsic-test/src/loongarch/parser.rs @@ -115,7 +115,7 @@ fn parse_intrinsic( arguments: ArgumentList { args: arguments }, results: result, arch_tags: Vec::new(), - extension: extension_for(name)?.to_string(), + extensions: vec![extension_for(name)?.to_string()], }) } diff --git a/crates/intrinsic-test/src/main.rs b/crates/intrinsic-test/src/main.rs index 91e76d8f5f..2e5647746a 100644 --- a/crates/intrinsic-test/src/main.rs +++ b/crates/intrinsic-test/src/main.rs @@ -4,12 +4,14 @@ extern crate log; mod arm; mod common; mod loongarch; +mod riscv; mod x86; use arm::Arm; use common::SupportedArchitecture; use common::cli::{Cli, ProcessedCli}; use loongarch::LoongArch; +use riscv::Riscv; use x86::X86; fn main() { @@ -28,6 +30,8 @@ fn main() { LoongArch::create(&processed_cli_options), processed_cli_options, ) + } else if processed_cli_options.target.starts_with("riscv") { + run(Riscv::create(&processed_cli_options), processed_cli_options); } else { unimplemented!("Unsupported target {}", processed_cli_options.target) } diff --git a/crates/intrinsic-test/src/riscv/intrinsic.rs b/crates/intrinsic-test/src/riscv/intrinsic.rs new file mode 100644 index 0000000000..562ddd3160 --- /dev/null +++ b/crates/intrinsic-test/src/riscv/intrinsic.rs @@ -0,0 +1,19 @@ +use crate::common::intrinsic_helpers::IntrinsicType; +use std::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, PartialEq)] +pub struct RiscvIntrinsicType(pub IntrinsicType); + +impl Deref for RiscvIntrinsicType { + type Target = IntrinsicType; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for RiscvIntrinsicType { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/intrinsic-test/src/riscv/mod.rs b/crates/intrinsic-test/src/riscv/mod.rs new file mode 100644 index 0000000000..ee8441b726 --- /dev/null +++ b/crates/intrinsic-test/src/riscv/mod.rs @@ -0,0 +1,104 @@ +mod intrinsic; +mod parser; +mod types; + +use std::path::Path; + +use crate::common::SupportedArchitecture; +use crate::common::cli::ProcessedCli; +use crate::common::intrinsic::Intrinsic; +use crate::common::intrinsic_helpers::TypeKind; +use crate::riscv::parser::get_intrinsics; +use intrinsic::RiscvIntrinsicType; + +#[derive(PartialEq)] +pub struct Riscv { + intrinsics: Vec>, +} + +impl SupportedArchitecture for Riscv { + type Type = RiscvIntrinsicType; + + fn intrinsics(&self) -> &[Intrinsic] { + &self.intrinsics + } + + fn create(cli_options: &ProcessedCli) -> Self { + let rv32 = cli_options.target.starts_with("riscv32"); + + let mut intrinsics = + load_intrinsics(&cli_options.filename).expect("Error parsing input file"); + + intrinsics.sort_by(|a, b| a.name.cmp(&b.name)); + intrinsics.dedup_by(|a, b| { + a.name == b.name && a.results == b.results && a.arguments == b.arguments + }); + + let sample_percentage: usize = cli_options.sample_percentage as usize; + let sample_size = (intrinsics.len() * sample_percentage) / 100; + + let intrinsics = intrinsics + .into_iter() + // Skip intrinsics that don't return a value. + .filter(|intrinsic| intrinsic.results.kind() != TypeKind::Void) + .filter(|intrinsic| !intrinsic.arguments.args.is_empty()) + .filter(|intrinsic| !cli_options.skip.contains(&intrinsic.name)) + .filter(|intrinsic| { + intrinsic.arch_tags.is_empty() || ((intrinsic.arch_tags[0] == "rv64") ^ rv32) + }) + .take(sample_size) + .collect::>(); + + Self { intrinsics } + } + + const NOTICE: &str = r#" +// This is a transient test file, not intended for distribution. Some aspects of the +// test are derived from RISC-V specification files, published under the same license as the +// `intrinsic-test` crate. + "#; + + const C_PRELUDE: &str = r#" +#include +#include +"#; + + const RUST_PRELUDE: &str = RUST_PRELUDE; + + const C_NAME_PREFIX: &str = "__riscv_"; + + fn c_compiler_flags(&self, cli_options: &ProcessedCli) -> Vec<&str> { + let arch_str = if cli_options.target.starts_with("riscv32") { + "-march=rv32gc_zbb_zbc_zk_zks" + } else { + "-march=rv64gc_zbb_zbc_zk_zks" + }; + vec![arch_str] + } + + fn predicate_function(_: u32) -> String { + unimplemented!("scalable vector intrinsics are currently unimplemented for RISC-V") + } +} + +const RUST_PRELUDE: &str = r#" +#![feature(riscv_ext_intrinsics)] + +#[cfg(target_arch = "riscv64")] +use core_arch::arch::riscv64::*; +#[cfg(target_arch = "riscv32")] +use core_arch::arch::riscv32::*; +"#; + +fn load_intrinsics(path: &Path) -> Result>, Box> { + if path.is_dir() { + let mut intrinsics = Vec::new(); + for spec in ["crypto.spec", "bitmanip.spec"] { + let spec_path = path.join(spec); + intrinsics.extend(get_intrinsics(&spec_path)?); + } + return Ok(intrinsics); + } + + get_intrinsics(path) +} diff --git a/crates/intrinsic-test/src/riscv/parser.rs b/crates/intrinsic-test/src/riscv/parser.rs new file mode 100644 index 0000000000..ea95eb708a --- /dev/null +++ b/crates/intrinsic-test/src/riscv/parser.rs @@ -0,0 +1,117 @@ +use std::{fs, path::Path}; + +use regex::regex; + +use crate::{ + common::{ + argument::{Argument, ArgumentList}, + constraint::Constraint, + intrinsic::Intrinsic, + intrinsic_helpers::{IntrinsicType, Sign, TypeKind}, + }, + riscv::{Riscv, intrinsic::RiscvIntrinsicType}, +}; + +// Parse the .spec files generated by stdarch-gen-riscv +pub fn get_intrinsics( + filename: &Path, +) -> Result>, Box> { + let mut intrinsics = vec![]; + let contents = fs::read_to_string(filename)?; + let mut lines = contents.lines(); + while let Some(mut line) = lines.next() { + let Some(name) = line.strip_prefix("name = ") else { + continue; + }; + + line = lines.next().unwrap(); + let arch = line.trim_prefix("arch = "); + let arch_tags = if arch == "rv32" { + vec!["rv32".to_string()] + } else if arch == "rv64" { + vec!["rv64".to_string()] + } else { + vec![] + }; + + line = lines.next().unwrap(); + let return_type = line.trim_prefix("return_type = "); + let results = parse_spec_type(return_type); + + line = lines.next().unwrap(); + let arguments = line.trim_prefix("arguments = "); + let arguments = ArgumentList { + args: arguments + .split(", ") + .enumerate() + .map(|(idx, arg)| parse_argument(arg, idx)) + .collect(), + }; + + line = lines.next().unwrap(); + let extensions = line + .trim_prefix("extensions = ") + .split(", ") + .map(ToString::to_string) + .collect(); + + intrinsics.push(Intrinsic { + name: name.to_string(), + arguments, + results, + arch_tags, + extensions, + }); + } + Ok(intrinsics) +} + +fn parse_spec_type(input: &str) -> RiscvIntrinsicType { + let (input, constant) = match input.strip_prefix("const ") { + Some(input) => (input, true), + None => (input, false), + }; + + let (kind, bit_len) = match input { + "uint32_t" => (TypeKind::Int(Sign::Unsigned), 32), + "uint64_t" => (TypeKind::Int(Sign::Unsigned), 64), + "int" => (TypeKind::Int(Sign::Signed), 32), + "unsigned" => (TypeKind::Int(Sign::Unsigned), 32), + c_type => panic!("unsupported RISC-V type {c_type}"), + }; + + RiscvIntrinsicType(IntrinsicType { + constant, + ptr_constant: false, + ptr: false, + kind, + bit_len: Some(bit_len), + simd_len: None, + vec_len: None, + }) +} + +fn parse_argument(input: &str, pos: usize) -> Argument { + let captures = regex!(r"^(.*?)(\w+)\s*$").captures(input).unwrap(); + let c_type = captures[1].trim(); + let ty = parse_spec_type(c_type); + let name = captures[2].trim().to_string(); + let constraint = ty.constant.then(|| constant_constraint(&name)); + Argument { + pos, + name, + ty, + constraint, + is_predicate: false, + } +} + +// The header files don't include information about the constraints on constant parameters, +// so we have to hard-code them instead +fn constant_constraint(name: &str) -> Constraint { + match name { + "bs" => Constraint::Range(0..4), + "rnum" => Constraint::Range(0..11), + name => panic!("unrecognized constant {name}"), + } +} diff --git a/crates/intrinsic-test/src/riscv/types.rs b/crates/intrinsic-test/src/riscv/types.rs new file mode 100644 index 0000000000..088c054b14 --- /dev/null +++ b/crates/intrinsic-test/src/riscv/types.rs @@ -0,0 +1,72 @@ +use crate::{ + common::intrinsic_helpers::{Sign, TypeDefinition, TypeKind}, + riscv::intrinsic::RiscvIntrinsicType, +}; + +impl TypeDefinition for RiscvIntrinsicType { + fn load_function(&self) -> String { + unimplemented!("internal error: reached unimplemented intrinsic kind {self:?}"); + } + + fn c_type(&self) -> String { + assert!( + !self.ptr && !self.ptr_constant && self.vec_len.is_none() && self.simd_len.is_none(), + "internal error: reached unimplemented intrinsic type {self:?}" + ); + + let TypeKind::Int(sign) = self.kind else { + panic!( + "internal error: reached unimplemented intrinsic type kind {:?}", + self.kind + ); + }; + let bit_len = self.bit_len.unwrap(); + let base_int_type = scalar_c_type_name(sign, bit_len); + + if self.constant { + format!("const {base_int_type}") + } else { + base_int_type.to_string() + } + } + + fn rust_type(&self) -> String { + assert!( + !self.ptr && !self.ptr_constant && self.vec_len.is_none() && self.simd_len.is_none(), + "internal error: reached unimplemented intrinsic type {self:?}" + ); + + let TypeKind::Int(sign) = self.kind else { + panic!( + "internal error: reached unimplemented intrinsic type kind {:?}", + self.kind + ); + }; + let bit_len = self.bit_len.unwrap(); + scalar_rust_type_name(sign, bit_len).to_string() + } +} + +fn scalar_c_type_name(sign: Sign, bit_len: u32) -> &'static str { + match (sign, bit_len) { + (Sign::Signed, 32) => "int32_t", + (Sign::Signed, 64) => "int64_t", + (Sign::Unsigned, 32) => "uint32_t", + (Sign::Unsigned, 64) => "uint64_t", + _ => unreachable!("unsupported RISC-V scalar width {bit_len}"), + } +} + +fn scalar_rust_type_name(sign: Sign, bit_len: u32) -> &'static str { + match (sign, bit_len) { + (Sign::Signed, 8) => "i8", + (Sign::Signed, 16) => "i16", + (Sign::Signed, 32) => "i32", + (Sign::Signed, 64) => "i64", + (Sign::Unsigned, 8) => "u8", + (Sign::Unsigned, 16) => "u16", + (Sign::Unsigned, 32) => "u32", + (Sign::Unsigned, 64) => "u64", + _ => unreachable!("unsupported RISC-V scalar width {bit_len}"), + } +} diff --git a/crates/intrinsic-test/src/x86/xml_parser.rs b/crates/intrinsic-test/src/x86/xml_parser.rs index 2296dffb64..d326286acc 100644 --- a/crates/intrinsic-test/src/x86/xml_parser.rs +++ b/crates/intrinsic-test/src/x86/xml_parser.rs @@ -144,6 +144,6 @@ fn xml_to_intrinsic(intr: XMLIntrinsic) -> Result, Box