From 9c3ba20023a36d74bf36a2c11e0afb08d8753ca2 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:00:12 +0200 Subject: [PATCH 01/25] benchtool: bench-hash added --- tools/benchtool/src/main.rs | 181 ++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/tools/benchtool/src/main.rs b/tools/benchtool/src/main.rs index bf6200e3d..caee97e04 100644 --- a/tools/benchtool/src/main.rs +++ b/tools/benchtool/src/main.rs @@ -467,6 +467,33 @@ enum Args { /// Benchmarks PolkaVM's memset. BenchMemset, + /// Benchmarks hash functions. + /// + /// Discovers the bench-hash artifacts (PVM blob and native libraries, + /// including variants like libbench_hash_simd.so) and measures their + /// `benchmark_(len, times) -> u64` exports over a grid of input + /// sizes, printing raw per-iteration times. Comparing artifacts (e.g. + /// PVM vs native) is up to the caller. + BenchHash { + /// Comma-separated input sizes, in bytes. + #[clap(long, default_value = "32,128,512,4096,65536,1048576")] + sizes: String, + + /// Target total bytes hashed per row; iteration count is derived as + /// max(1, total_bytes / size). + #[clap(long, default_value_t = 32 * 1024 * 1024)] + total_bytes: u64, + + /// Output CSV (artifact,algo,size,ns_per_iter,times,checksum) instead of + /// human-readable rows. + #[clap(long)] + csv: bool, + + /// Hash algorithm names; each is resolved as a `benchmark_` export. + #[clap(required = true)] + algos: Vec, + }, + /// Benchmarks ecalli overhead. BenchEcalli { #[clap(long)] @@ -826,6 +853,160 @@ fn main() { } } } + Args::BenchHash { sizes, total_bytes, csv, algos } => { + let sizes: Vec = sizes + .split(',') + .map(|s| s.trim().parse().expect("invalid --sizes entry")) + .collect(); + + struct Row<'a> { + algo: &'a str, + size: u64, + times: u64, + elapsed: Duration, + checksum: u64, + } + + fn print_row(csv: bool, artifact: &str, row: Row) { + if csv { + println!( + "{artifact},{algo},{size},{ns_per_iter:.2},{times},{checksum:016x}", + algo = row.algo, + size = row.size, + ns_per_iter = row.elapsed.as_nanos() as f64 / row.times as f64, + times = row.times, + checksum = row.checksum, + ); + } else { + println!( + "{artifact} {algo:<12} {size:>8}: {time_per_iter}/iter (x{times}) checksum={checksum:016x}", + algo = row.algo, + size = row.size, + time_per_iter = format_time_with_div(row.elapsed, row.times as u32), + times = row.times, + checksum = row.checksum, + ); + } + } + + if csv { + println!("artifact,algo,size,ns_per_iter,times,checksum"); + } + + enum Artifact { + Pvm(PathBuf), + Native(PathBuf), + } + + // Discover artifacts like `benchmark` does. This picks up the standard + // bench-hash artifacts from guest-programs/target as well as any variant + // there or in the current directory (e.g. a `libbench_hash_simd.so` is + // discovered as "hash-simd"). + let mut artifacts = Vec::new(); + for benchmark in find_benchmarks().unwrap() { + if benchmark.name != "hash" && !benchmark.name.starts_with("hash-") { + continue; + } + match benchmark.kind { + BenchmarkKind::PolkaVM64 => artifacts.push(Artifact::Pvm(benchmark.path)), + BenchmarkKind::Native => artifacts.push(Artifact::Native(benchmark.path)), + _ => {} + } + } + if artifacts.is_empty() { + eprintln!( + "no bench-hash artifacts found; build them with \ + guest-programs/build-benchmarks.sh and guest-programs/build-hash-native.sh" + ); + std::process::exit(1); + } + + for artifact in artifacts { + match artifact { + Artifact::Pvm(path) => { + let config = polkavm::Config::from_env().unwrap(); + let engine = polkavm::Engine::new(&config).unwrap(); + let raw_blob = std::fs::read(&path).unwrap(); + let blob = polkavm::ProgramBlob::parse(raw_blob.into()).unwrap(); + let mut module_config = polkavm::ModuleConfig::default(); + module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); + let module = polkavm::Module::from_blob(&engine, &module_config, blob).unwrap(); + let linker = polkavm::Linker::<()>::new(); + let instance_pre = linker.instantiate_pre(&module).unwrap(); + let mut instance = instance_pre.instantiate().unwrap(); + let artifact = path.file_stem().unwrap().to_string_lossy().into_owned(); + + instance.set_gas(polkavm::Gas::MAX); + instance.call_typed(&mut (), "initialize", ()).unwrap(); + + for algo in &algos { + let export = format!("benchmark_{algo}"); + for &size in &sizes { + let times = (total_bytes / size).max(1); + let mut measure = || -> (Duration, u64) { + // Reset the input buffer so every measurement is + // self-contained and checksums are comparable + // across runs regardless of row order. + instance.set_gas(polkavm::Gas::MAX); + instance.call_typed(&mut (), "initialize", ()).unwrap(); + instance.set_gas(polkavm::Gas::MAX); + let timestamp = std::time::Instant::now(); + let checksum: u64 = instance + .call_typed_and_get_result(&mut (), &*export, (size, times)) + .unwrap(); + (timestamp.elapsed(), checksum) + }; + measure(); // Warmup. + let (elapsed, checksum) = measure(); + print_row(csv, &artifact, Row { algo, size, times, elapsed, checksum }); + } + } + } + Artifact::Native(path) => { + #[cfg(feature = "native")] + { + // A bare filename would be looked up in the system library + // search path by dlopen, not in the current directory. + let path = path.canonicalize().unwrap_or_else(|error| { + eprintln!("failed to resolve {path:?}: {error}"); + std::process::exit(1); + }); + let library = unsafe { libloading::Library::new(&path) }.unwrap(); + let artifact = path.file_stem().unwrap().to_string_lossy().into_owned(); + + let initialize = unsafe { + library.get::(b"initialize").unwrap() + }; + + for algo in &algos { + let export = format!("benchmark_{algo}"); + let func: libloading::Symbol u64> = + unsafe { library.get(export.as_bytes()) }.unwrap(); + for &size in &sizes { + let times = (total_bytes / size).max(1); + let mut measure = || -> (Duration, u64) { + // See the PVM path: reset state for comparable checksums. + unsafe { initialize() }; + let timestamp = std::time::Instant::now(); + let checksum = unsafe { func(size, times) }; + (timestamp.elapsed(), checksum) + }; + measure(); // Warmup. + let (elapsed, checksum) = measure(); + print_row(csv, &artifact, Row { algo, size, times, elapsed, checksum }); + } + } + } + #[cfg(not(feature = "native"))] + { + let _ = path; + eprintln!("native libraries require benchtool to be built with the 'native' feature"); + std::process::exit(1); + } + } + } + } + } Args::BenchEcalli { interpreter } => { use polkavm_common::program::{asm, InstructionSetKind, ProgramBlob}; let mut builder = polkavm_common::writer::ProgramBlobBuilder::new(InstructionSetKind::JamV1); From bc2efaa0e9d48951cb37237c68cad14c6e5403fb Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:01:05 +0200 Subject: [PATCH 02/25] bench-hash: initial version added --- guest-programs/Cargo.lock | 59 +++++++++++++++++++ guest-programs/Cargo.toml | 1 + guest-programs/bench-hash/Cargo.toml | 37 ++++++++++++ guest-programs/bench-hash/src/main.rs | 85 +++++++++++++++++++++++++++ guest-programs/build-benchmarks.sh | 1 + guest-programs/build-hash-native.sh | 19 ++++++ 6 files changed, 202 insertions(+) create mode 100644 guest-programs/bench-hash/Cargo.toml create mode 100644 guest-programs/bench-hash/src/main.rs create mode 100755 guest-programs/build-hash-native.sh diff --git a/guest-programs/Cargo.lock b/guest-programs/Cargo.lock index d39f0f0f8..29292c5fc 100644 --- a/guest-programs/Cargo.lock +++ b/guest-programs/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "bench-ed25519" version = "0.1.0" @@ -12,6 +24,17 @@ dependencies = [ "simplealloc", ] +[[package]] +name = "bench-hash" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "picoalloc", + "polkavm-derive", + "sha2", + "sha3", +] + [[package]] name = "bench-memset" version = "0.1.0" @@ -57,6 +80,17 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -78,6 +112,12 @@ version = "0.1.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7925a77545f37a18e152a4aeaeb9a220309022067cafcfa2ecebe9ec55d36ccb" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -194,6 +234,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" version = "0.2.186" @@ -308,6 +357,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "signature" version = "2.2.0" diff --git a/guest-programs/Cargo.toml b/guest-programs/Cargo.toml index 3711d9b80..256200c05 100644 --- a/guest-programs/Cargo.toml +++ b/guest-programs/Cargo.toml @@ -19,6 +19,7 @@ members = [ "bench-minimal", "bench-memset", "bench-ed25519", + "bench-hash", "bench-pinky", "bench-prime-sieve", diff --git a/guest-programs/bench-hash/Cargo.toml b/guest-programs/bench-hash/Cargo.toml new file mode 100644 index 000000000..d65156f61 --- /dev/null +++ b/guest-programs/bench-hash/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bench-hash" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_hash" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-hash" +path = "src/main.rs" + +[dependencies] +blake2b_simd = { version = "1", default-features = false } +sha2 = { version = "0.10", default-features = false } +sha3 = { version = "0.10", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +# To build a SIMD-accelerated native library for comparison, enable the CPU +# features at compile time (runtime dispatch would require std, which +# conflicts with this crate being no_std): +# +# RUSTFLAGS="-C target-cpu=native" cargo build \ +# --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash +# +# and copy the resulting libbench_hash.so aside (e.g. as libbench_hash_simd.so +# next to benchtool, where it is auto-discovered as "hash-simd") before it is +# overwritten by a regular build. + +[lints] +workspace = true diff --git a/guest-programs/bench-hash/src/main.rs b/guest-programs/bench-hash/src/main.rs new file mode 100644 index 000000000..8380fb228 --- /dev/null +++ b/guest-programs/bench-hash/src/main.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +const MAX_LEN: usize = 1024 * 1024; + +struct State { + memory: alloc::vec::Vec, +} + +define_benchmark! { + heap_size = MAX_LEN + 64 * 1024, + state = State { + memory: alloc::vec::Vec::new(), + }, +} + +fn benchmark_initialize(state: &mut State) { + state.memory.resize(MAX_LEN, 0); + for (index, byte) in state.memory.iter_mut().enumerate() { + *byte = index as u8; + } +} + +// Entry point for the generic harness; the real benchmarks are the +// parameterized `benchmark_*` exports below, driven by benchtool's +// `bench-hash` subcommand. +fn benchmark_run(state: &mut State) { + core::hint::black_box(chained(&mut state.memory, 32, 1, blake2b_once)); +} + +fn blake2b_once(input: &[u8], out: &mut [u8; 32]) { + let hash = blake2b_simd::Params::new().hash_length(32).hash(input); + out.copy_from_slice(hash.as_bytes()); +} + +fn keccak256_once(input: &[u8], out: &mut [u8; 32]) { + use sha3::Digest; + let mut hasher = sha3::Keccak256::new(); + hasher.update(input); + out.copy_from_slice(&hasher.finalize()); +} + +fn sha256_once(input: &[u8], out: &mut [u8; 32]) { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(input); + out.copy_from_slice(&hasher.finalize()); +} + +/// Hashes `memory[..len]` `times` times, feeding each round's output back +/// into the start of the buffer so every iteration depends on the previous +/// one (no loop-invariant hoisting), and returns a fold of the final output +/// so the whole chain is observable (no dead-code elimination). +fn chained( + memory: &mut [u8], + len: u64, + times: u64, + hash_once: impl Fn(&[u8], &mut [u8; 32]), +) -> u64 { + let len = (len as usize).min(memory.len()).max(1); + let mut out = [0u8; 32]; + for _ in 0..times { + hash_once(&memory[..len], &mut out); + let feedback = len.min(32); + memory[..feedback].copy_from_slice(&out[..feedback]); + } + u64::from_le_bytes(out[..8].try_into().unwrap()) +} + +macro_rules! export_hash_benchmark { + ($name:ident, $hash_once:expr) => { + #[cfg_attr(target_env = "polkavm", polkavm_derive::polkavm_export)] + #[no_mangle] + pub extern "C" fn $name(len: u64, times: u64) -> u64 { + let state = unsafe { &mut *core::ptr::addr_of_mut!(STATE) }; + chained(&mut state.memory, len, times, $hash_once) + } + }; +} + +export_hash_benchmark!(benchmark_blake2b, blake2b_once); +export_hash_benchmark!(benchmark_keccak256, keccak256_once); +export_hash_benchmark!(benchmark_sha256, sha256_once); diff --git a/guest-programs/build-benchmarks.sh b/guest-programs/build-benchmarks.sh index 01f185170..c31c45969 100755 --- a/guest-programs/build-benchmarks.sh +++ b/guest-programs/build-benchmarks.sh @@ -137,6 +137,7 @@ build_benchmark "bench-minimal" build_benchmark "bench-pinky" build_benchmark "bench-prime-sieve" build_benchmark "bench-ed25519" +build_benchmark "bench-hash" if [ "${SOLANA_PLATFORM_TOOLS_DIR:-}" != "" ]; then unset SOLANA_PLATFORM_TOOLS_DIR diff --git a/guest-programs/build-hash-native.sh b/guest-programs/build-hash-native.sh new file mode 100755 index 000000000..c0562061d --- /dev/null +++ b/guest-programs/build-hash-native.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# Builds the bench-hash native library in both variants: +# libbench_hash.so - portable implementations +# libbench_hash_simd.so - CPU features enabled at compile time (e.g. AVX2) +# +# Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool's +# `bench-hash` subcommand auto-discovers them as "hash" and "hash-simd". + +set -ex +cd "${0%/*}" + +out=target/x86_64-unknown-linux-gnu/release + +RUSTFLAGS="-C target-cpu=native" cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash +cp "$out/libbench_hash.so" "$out/libbench_hash_simd.so" + +# Rebuild without the CPU features; overwrites libbench_hash.so with the portable variant. +cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash From 6d8903e258f123dbd092d82f1ed199dc91415d29 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:54:27 +0200 Subject: [PATCH 03/25] bench-hash: more algos added --- guest-programs/Cargo.lock | 7 +++ guest-programs/bench-hash/Cargo.toml | 1 + guest-programs/bench-hash/src/main.rs | 72 +++++++++++++++++++++------ 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/guest-programs/Cargo.lock b/guest-programs/Cargo.lock index 29292c5fc..404a185a0 100644 --- a/guest-programs/Cargo.lock +++ b/guest-programs/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "polkavm-derive", "sha2", "sha3", + "twox-hash", ] [[package]] @@ -419,6 +420,12 @@ dependencies = [ "simplealloc", ] +[[package]] +name = "twox-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" + [[package]] name = "typenum" version = "1.20.0" diff --git a/guest-programs/bench-hash/Cargo.toml b/guest-programs/bench-hash/Cargo.toml index d65156f61..5d0e4d24d 100644 --- a/guest-programs/bench-hash/Cargo.toml +++ b/guest-programs/bench-hash/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" blake2b_simd = { version = "1", default-features = false } sha2 = { version = "0.10", default-features = false } sha3 = { version = "0.10", default-features = false } +twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } picoalloc = { workspace = true } [target.'cfg(target_env = "polkavm")'.dependencies] diff --git a/guest-programs/bench-hash/src/main.rs b/guest-programs/bench-hash/src/main.rs index 8380fb228..cd4cd9be9 100644 --- a/guest-programs/bench-hash/src/main.rs +++ b/guest-programs/bench-hash/src/main.rs @@ -27,26 +27,60 @@ fn benchmark_initialize(state: &mut State) { // parameterized `benchmark_*` exports below, driven by benchtool's // `bench-hash` subcommand. fn benchmark_run(state: &mut State) { - core::hint::black_box(chained(&mut state.memory, 32, 1, blake2b_once)); + core::hint::black_box(chained(&mut state.memory, 32, 1, blake2_256_once)); } -fn blake2b_once(input: &[u8], out: &mut [u8; 32]) { - let hash = blake2b_simd::Params::new().hash_length(32).hash(input); - out.copy_from_slice(hash.as_bytes()); +/// The output buffer fits the largest digest (keccak_512); each hash function +/// writes its own digest length and returns it. +type Output = [u8; 64]; + +fn blake2b_once(input: &[u8], out: &mut Output, hash_length: usize) -> usize { + let hash = blake2b_simd::Params::new().hash_length(hash_length).hash(input); + out[..hash_length].copy_from_slice(hash.as_bytes()); + hash_length +} + +fn blake2_128_once(input: &[u8], out: &mut Output) -> usize { + blake2b_once(input, out, 16) } -fn keccak256_once(input: &[u8], out: &mut [u8; 32]) { +fn blake2_256_once(input: &[u8], out: &mut Output) -> usize { + blake2b_once(input, out, 32) +} + +fn keccak_256_once(input: &[u8], out: &mut Output) -> usize { use sha3::Digest; let mut hasher = sha3::Keccak256::new(); hasher.update(input); - out.copy_from_slice(&hasher.finalize()); + out[..32].copy_from_slice(&hasher.finalize()); + 32 +} + +fn keccak_512_once(input: &[u8], out: &mut Output) -> usize { + use sha3::Digest; + let mut hasher = sha3::Keccak512::new(); + hasher.update(input); + out[..64].copy_from_slice(&hasher.finalize()); + 64 } -fn sha256_once(input: &[u8], out: &mut [u8; 32]) { +fn sha2_256_once(input: &[u8], out: &mut Output) -> usize { use sha2::Digest; let mut hasher = sha2::Sha256::new(); hasher.update(input); - out.copy_from_slice(&hasher.finalize()); + out[..32].copy_from_slice(&hasher.finalize()); + 32 +} + +/// N seeded XxHash64 passes, like `sp_core::hashing::twox_*`. +fn twox_once(input: &[u8], out: &mut Output) -> usize { + use core::hash::Hasher; + for seed in 0..WORDS { + let mut hasher = twox_hash::XxHash64::with_seed(seed as u64); + hasher.write(input); + out[seed * 8..(seed + 1) * 8].copy_from_slice(&hasher.finish().to_le_bytes()); + } + WORDS * 8 } /// Hashes `memory[..len]` `times` times, feeding each round's output back @@ -57,15 +91,17 @@ fn chained( memory: &mut [u8], len: u64, times: u64, - hash_once: impl Fn(&[u8], &mut [u8; 32]), + hash_once: impl Fn(&[u8], &mut Output) -> usize, ) -> u64 { let len = (len as usize).min(memory.len()).max(1); - let mut out = [0u8; 32]; + let mut out = [0u8; 64]; + let mut out_len = 8; for _ in 0..times { - hash_once(&memory[..len], &mut out); - let feedback = len.min(32); + out_len = hash_once(&memory[..len], &mut out); + let feedback = len.min(out_len); memory[..feedback].copy_from_slice(&out[..feedback]); } + let _ = out_len; u64::from_le_bytes(out[..8].try_into().unwrap()) } @@ -80,6 +116,12 @@ macro_rules! export_hash_benchmark { }; } -export_hash_benchmark!(benchmark_blake2b, blake2b_once); -export_hash_benchmark!(benchmark_keccak256, keccak256_once); -export_hash_benchmark!(benchmark_sha256, sha256_once); +// Named after the `sp_io::hashing` host functions they mirror. +export_hash_benchmark!(benchmark_blake2_128, blake2_128_once); +export_hash_benchmark!(benchmark_blake2_256, blake2_256_once); +export_hash_benchmark!(benchmark_keccak_256, keccak_256_once); +export_hash_benchmark!(benchmark_keccak_512, keccak_512_once); +export_hash_benchmark!(benchmark_sha2_256, sha2_256_once); +export_hash_benchmark!(benchmark_twox_64, twox_once::<1>); +export_hash_benchmark!(benchmark_twox_128, twox_once::<2>); +export_hash_benchmark!(benchmark_twox_256, twox_once::<4>); From 9c97288ecde9d78d1ed8e87b0df62811b3b492b1 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:30:21 +0200 Subject: [PATCH 04/25] memcpy: poc --- guest-programs/bench-common.rs | 48 ++++++------ guest-programs/bench-hash/src/main.rs | 8 +- tools/benchtool/process-results.sh | 102 ++++++++++++++++++++++++++ tools/benchtool/src/main.rs | 11 +++ 4 files changed, 140 insertions(+), 29 deletions(-) create mode 100755 tools/benchtool/process-results.sh diff --git a/guest-programs/bench-common.rs b/guest-programs/bench-common.rs index 60b4ff77b..4c4bfe11e 100644 --- a/guest-programs/bench-common.rs +++ b/guest-programs/bench-common.rs @@ -76,30 +76,30 @@ macro_rules! define_benchmark { }; } -#[cfg(target_env = "polkavm")] -#[no_mangle] -unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { - unsafe { - for offset in 0..n { - *dst.add(offset) = *src.add(offset); - } - } - - dst -} - -#[cfg(target_env = "polkavm")] -#[no_mangle] -unsafe extern "C" fn memset(dst: *mut u8, value: i32, n: usize) -> *mut u8 { - unsafe { - for offset in 0..n { - *dst.add(offset) = value as u8; - } - } - - dst -} - +// #[cfg(target_env = "polkavm")] +// #[no_mangle] +// unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { +// unsafe { +// for offset in 0..n { +// *dst.add(offset) = *src.add(offset); +// } +// } +// +// dst +// } +// +// #[cfg(target_env = "polkavm")] +// #[no_mangle] +// unsafe extern "C" fn memset(dst: *mut u8, value: i32, n: usize) -> *mut u8 { +// unsafe { +// for offset in 0..n { +// *dst.add(offset) = value as u8; +// } +// } +// +// dst +// } +// #[cfg(target_os = "solana")] #[no_mangle] extern "C" fn sol_memcpy_(dst: *mut u8, src: *const u8, n: usize) { diff --git a/guest-programs/bench-hash/src/main.rs b/guest-programs/bench-hash/src/main.rs index cd4cd9be9..123585d4b 100644 --- a/guest-programs/bench-hash/src/main.rs +++ b/guest-programs/bench-hash/src/main.rs @@ -85,8 +85,8 @@ fn twox_once(input: &[u8], out: &mut Output) -> usize { /// Hashes `memory[..len]` `times` times, feeding each round's output back /// into the start of the buffer so every iteration depends on the previous -/// one (no loop-invariant hoisting), and returns a fold of the final output -/// so the whole chain is observable (no dead-code elimination). +/// one (no loop-invariant hoisting), and returns the first 8 bytes of the +/// final digest so the whole chain is observable (no dead-code elimination). fn chained( memory: &mut [u8], len: u64, @@ -95,13 +95,11 @@ fn chained( ) -> u64 { let len = (len as usize).min(memory.len()).max(1); let mut out = [0u8; 64]; - let mut out_len = 8; for _ in 0..times { - out_len = hash_once(&memory[..len], &mut out); + let out_len = hash_once(&memory[..len], &mut out); let feedback = len.min(out_len); memory[..feedback].copy_from_slice(&out[..feedback]); } - let _ = out_len; u64::from_le_bytes(out[..8].try_into().unwrap()) } diff --git a/tools/benchtool/process-results.sh b/tools/benchtool/process-results.sh new file mode 100755 index 000000000..15720a627 --- /dev/null +++ b/tools/benchtool/process-results.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +# Turns `benchtool bench-hash --csv` output into per-algorithm markdown tables. +# +# Usage: +# benchtool bench-hash --csv blake2b sha256 keccak256 > results.csv +# ./process-results.sh results.csv > result-table.md +# +# The artifact whose name ends in `_simd` is used as the baseline for ratios; +# if none is present, the first artifact (in input order) is the baseline. +# Checksums are cross-checked: rows of the same (algo, size) must agree. + +set -eu + +awk -F, ' +NR == 1 && $1 == "artifact" { next } # header +NF < 6 { next } +{ + artifact = $1; algo = $2; size = $3; ns = $4; checksum = $6 + + if (!(artifact in artifact_seen)) { + artifact_seen[artifact] = ++artifact_count + artifacts[artifact_count] = artifact + if (artifact ~ /_simd$/) baseline = artifact + } + if (!((algo, size) in row_seen)) { + row_seen[algo, size] = 1 + if (!(algo in algo_seen)) { algo_seen[algo] = ++algo_count; algos[algo_count] = algo } + sizes_for[algo] = sizes_for[algo] " " size + } + + time[artifact, algo, size] = ns + + if ((algo, size) in want_checksum) { + if (want_checksum[algo, size] != checksum) { + printf "WARNING: checksum mismatch for %s/%s: %s vs %s\n", \ + algo, size, want_checksum[algo, size], checksum > "/dev/stderr" + } + } else { + want_checksum[algo, size] = checksum + } +} + +function fmt(ns) { + if (ns < 1000) return sprintf("%.0f ns", ns) + if (ns < 1000000) return sprintf("%.2f µs", ns / 1000) + return sprintf("%.2f ms", ns / 1000000) +} + +function fmt_size(bytes) { + if (bytes < 1024) return bytes " B" + if (bytes < 1048576) return (bytes / 1024) " KiB" + return (bytes / 1048576) " MiB" +} + +END { + if (baseline == "") baseline = artifacts[1] + + # Reorder so the baseline column comes first. + ordered_count = 0 + ordered[++ordered_count] = baseline + for (i = 1; i <= artifact_count; i++) { + if (artifacts[i] != baseline) ordered[++ordered_count] = artifacts[i] + } + for (i = 1; i <= artifact_count; i++) artifacts[i] = ordered[i] + + for (a = 1; a <= algo_count; a++) { + algo = algos[a] + printf "## %s\n\n", algo + + printf "| size |" + for (i = 1; i <= artifact_count; i++) { + printf " %s |", artifacts[i] + if (artifacts[i] != baseline) printf " vs %s |", baseline + } + printf "\n|---:|" + for (i = 1; i <= artifact_count; i++) { + printf "---:|" + if (artifacts[i] != baseline) printf "---:|" + } + printf "\n" + + n = split(sizes_for[algo], size_list, " ") + for (s = 1; s <= n; s++) { + size = size_list[s] + printf "| %s |", fmt_size(size) + base_ns = time[baseline, algo, size] + for (i = 1; i <= artifact_count; i++) { + ns = time[artifacts[i], algo, size] + if (ns == "") { printf " - |"; if (artifacts[i] != baseline) printf " - |"; continue } + printf " %s |", fmt(ns) + if (artifacts[i] != baseline) { + if (base_ns > 0) printf " %.2fx |", ns / base_ns + else printf " - |" + } + } + printf "\n" + } + printf "\n" + } +} +' "$@" diff --git a/tools/benchtool/src/main.rs b/tools/benchtool/src/main.rs index caee97e04..744811891 100644 --- a/tools/benchtool/src/main.rs +++ b/tools/benchtool/src/main.rs @@ -854,9 +854,20 @@ fn main() { } } Args::BenchHash { sizes, total_bytes, csv, algos } => { + disable_aslr(); + + // bench-hash's input buffer size; larger sizes would silently clamp + // in the guest and mislabel the row. + const MAX_LEN: u64 = 1024 * 1024; let sizes: Vec = sizes .split(',') .map(|s| s.trim().parse().expect("invalid --sizes entry")) + .inspect(|&size| { + assert!( + (1..=MAX_LEN).contains(&size), + "--sizes entries must be in 1..={MAX_LEN} (got {size})" + ); + }) .collect(); struct Row<'a> { From 35ba9defe53a2f254824106ea0a785bdde782318 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:38:58 +0200 Subject: [PATCH 05/25] memcpy: feature added --- guest-programs/bench-common.rs | 48 ++++++++++----------- guest-programs/bench-ed25519/Cargo.toml | 4 ++ guest-programs/bench-hash/Cargo.toml | 4 ++ guest-programs/bench-memset/Cargo.toml | 4 ++ guest-programs/bench-minimal/Cargo.toml | 4 ++ guest-programs/bench-pinky/Cargo.toml | 4 ++ guest-programs/bench-prime-sieve/Cargo.toml | 4 ++ 7 files changed, 48 insertions(+), 24 deletions(-) diff --git a/guest-programs/bench-common.rs b/guest-programs/bench-common.rs index 4c4bfe11e..a2b27d381 100644 --- a/guest-programs/bench-common.rs +++ b/guest-programs/bench-common.rs @@ -76,30 +76,30 @@ macro_rules! define_benchmark { }; } -// #[cfg(target_env = "polkavm")] -// #[no_mangle] -// unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { -// unsafe { -// for offset in 0..n { -// *dst.add(offset) = *src.add(offset); -// } -// } -// -// dst -// } -// -// #[cfg(target_env = "polkavm")] -// #[no_mangle] -// unsafe extern "C" fn memset(dst: *mut u8, value: i32, n: usize) -> *mut u8 { -// unsafe { -// for offset in 0..n { -// *dst.add(offset) = value as u8; -// } -// } -// -// dst -// } -// +#[cfg(all(target_env = "polkavm", not(feature = "builtins-mem")))] +#[no_mangle] +unsafe extern "C" fn memcpy(dst: *mut u8, src: *const u8, n: usize) -> *mut u8 { + unsafe { + for offset in 0..n { + *dst.add(offset) = *src.add(offset); + } + } + + dst +} + +#[cfg(all(target_env = "polkavm", not(feature = "builtins-mem")))] +#[no_mangle] +unsafe extern "C" fn memset(dst: *mut u8, value: i32, n: usize) -> *mut u8 { + unsafe { + for offset in 0..n { + *dst.add(offset) = value as u8; + } + } + + dst +} + #[cfg(target_os = "solana")] #[no_mangle] extern "C" fn sol_memcpy_(dst: *mut u8, src: *const u8, n: usize) { diff --git a/guest-programs/bench-ed25519/Cargo.toml b/guest-programs/bench-ed25519/Cargo.toml index 3ada174a4..b67aa48fa 100644 --- a/guest-programs/bench-ed25519/Cargo.toml +++ b/guest-programs/bench-ed25519/Cargo.toml @@ -27,3 +27,7 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } [lints] workspace = true + +[features] +# Link compiler_builtins' word-wise memcpy/memset +builtins-mem = [] diff --git a/guest-programs/bench-hash/Cargo.toml b/guest-programs/bench-hash/Cargo.toml index 5d0e4d24d..979d9eb9a 100644 --- a/guest-programs/bench-hash/Cargo.toml +++ b/guest-programs/bench-hash/Cargo.toml @@ -34,5 +34,9 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } # next to benchtool, where it is auto-discovered as "hash-simd") before it is # overwritten by a regular build. +[features] +default = ["builtins-mem"] +builtins-mem = [] + [lints] workspace = true diff --git a/guest-programs/bench-memset/Cargo.toml b/guest-programs/bench-memset/Cargo.toml index c09ec2fe3..7335963a7 100644 --- a/guest-programs/bench-memset/Cargo.toml +++ b/guest-programs/bench-memset/Cargo.toml @@ -22,3 +22,7 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } [lints] workspace = true + +[features] +# Link compiler_builtins' word-wise memcpy/memset +builtins-mem = [] diff --git a/guest-programs/bench-minimal/Cargo.toml b/guest-programs/bench-minimal/Cargo.toml index f68b82d6b..665cf31fb 100644 --- a/guest-programs/bench-minimal/Cargo.toml +++ b/guest-programs/bench-minimal/Cargo.toml @@ -24,3 +24,7 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } [lints] workspace = true + +[features] +# Link compiler_builtins' word-wise memcpy/memset +builtins-mem = [] diff --git a/guest-programs/bench-pinky/Cargo.toml b/guest-programs/bench-pinky/Cargo.toml index 208ad9546..cdf412373 100644 --- a/guest-programs/bench-pinky/Cargo.toml +++ b/guest-programs/bench-pinky/Cargo.toml @@ -27,3 +27,7 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } [lints] workspace = true + +[features] +# Link compiler_builtins' word-wise memcpy/memset +builtins-mem = [] diff --git a/guest-programs/bench-prime-sieve/Cargo.toml b/guest-programs/bench-prime-sieve/Cargo.toml index cc46ba79e..375466b92 100644 --- a/guest-programs/bench-prime-sieve/Cargo.toml +++ b/guest-programs/bench-prime-sieve/Cargo.toml @@ -29,3 +29,7 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } [lints] workspace = true + +[features] +# Link compiler_builtins' word-wise memcpy/memset +builtins-mem = [] From 5ab207c70e975855d55ed4bf46ae0900c4b3df44 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:05:40 +0200 Subject: [PATCH 06/25] added: +unaligned-scalar-mem --- .../targets/legacy/riscv32emac-unknown-none-polkavm.json | 2 +- .../targets/legacy/riscv64emac-unknown-none-polkavm.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/polkavm-linker/targets/legacy/riscv32emac-unknown-none-polkavm.json b/crates/polkavm-linker/targets/legacy/riscv32emac-unknown-none-polkavm.json index 3687cd591..5b4c477b6 100644 --- a/crates/polkavm-linker/targets/legacy/riscv32emac-unknown-none-polkavm.json +++ b/crates/polkavm-linker/targets/legacy/riscv32emac-unknown-none-polkavm.json @@ -5,7 +5,7 @@ "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", "eh-frame-header": false, "emit-debug-gdb-scripts": false, - "features": "+e,+m,+a,+c,+zbb,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov", + "features": "+e,+m,+a,+c,+zbb,+unaligned-scalar-mem,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov", "linker": "rust-lld", "linker-flavor": "ld.lld", "llvm-abiname": "ilp32e", diff --git a/crates/polkavm-linker/targets/legacy/riscv64emac-unknown-none-polkavm.json b/crates/polkavm-linker/targets/legacy/riscv64emac-unknown-none-polkavm.json index 035ea8025..e484f295f 100644 --- a/crates/polkavm-linker/targets/legacy/riscv64emac-unknown-none-polkavm.json +++ b/crates/polkavm-linker/targets/legacy/riscv64emac-unknown-none-polkavm.json @@ -5,7 +5,7 @@ "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S64", "eh-frame-header": false, "emit-debug-gdb-scripts": false, - "features": "+e,+m,+a,+c,+zbb,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov", + "features": "+e,+m,+a,+c,+zbb,+unaligned-scalar-mem,+auipc-addi-fusion,+ld-add-fusion,+lui-addi-fusion,+xtheadcondmov", "linker": "rust-lld", "linker-flavor": "ld.lld", "llvm-abiname": "lp64e", From 528becf210a9c7130774c74597994980997cec04 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:04:19 +0200 Subject: [PATCH 07/25] crypto benchmarks added --- guest-programs/Cargo.lock | 408 +++++++++++++++++- guest-programs/Cargo.toml | 6 + guest-programs/bench-ecdsa-k256/Cargo.toml | 31 ++ guest-programs/bench-ecdsa-k256/src/main.rs | 39 ++ guest-programs/bench-ecdsa-libsecp/Cargo.toml | 32 ++ .../bench-ecdsa-libsecp/src/main.rs | 41 ++ guest-programs/bench-ed25519-zebra/Cargo.toml | 29 ++ .../bench-ed25519-zebra/src/main.rs | 39 ++ guest-programs/bench-recover-k256/Cargo.toml | 30 ++ guest-programs/bench-recover-k256/src/main.rs | 36 ++ .../bench-recover-libsecp/Cargo.toml | 31 ++ .../bench-recover-libsecp/src/main.rs | 34 ++ guest-programs/bench-sr25519/Cargo.toml | 30 ++ guest-programs/bench-sr25519/src/main.rs | 47 ++ guest-programs/build-benchmarks.sh | 6 + guest-programs/ecdsa-fixtures.rs | 35 ++ 16 files changed, 871 insertions(+), 3 deletions(-) create mode 100644 guest-programs/bench-ecdsa-k256/Cargo.toml create mode 100644 guest-programs/bench-ecdsa-k256/src/main.rs create mode 100644 guest-programs/bench-ecdsa-libsecp/Cargo.toml create mode 100644 guest-programs/bench-ecdsa-libsecp/src/main.rs create mode 100644 guest-programs/bench-ed25519-zebra/Cargo.toml create mode 100644 guest-programs/bench-ed25519-zebra/src/main.rs create mode 100644 guest-programs/bench-recover-k256/Cargo.toml create mode 100644 guest-programs/bench-recover-k256/src/main.rs create mode 100644 guest-programs/bench-recover-libsecp/Cargo.toml create mode 100644 guest-programs/bench-recover-libsecp/src/main.rs create mode 100644 guest-programs/bench-sr25519/Cargo.toml create mode 100644 guest-programs/bench-sr25519/src/main.rs create mode 100644 guest-programs/ecdsa-fixtures.rs diff --git a/guest-programs/Cargo.lock b/guest-programs/Cargo.lock index 1357036b4..587337195 100644 --- a/guest-programs/Cargo.lock +++ b/guest-programs/Cargo.lock @@ -14,6 +14,44 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bench-ecdsa-k256" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "k256", + "picoalloc", + "polkavm-derive", +] + +[[package]] +name = "bench-ecdsa-libsecp" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "libsecp256k1", + "picoalloc", + "polkavm-derive", +] + [[package]] name = "bench-ed25519" version = "0.1.0" @@ -24,6 +62,15 @@ dependencies = [ "simplealloc", ] +[[package]] +name = "bench-ed25519-zebra" +version = "0.1.0" +dependencies = [ + "ed25519-zebra", + "picoalloc", + "polkavm-derive", +] + [[package]] name = "bench-hash" version = "0.1.0" @@ -75,6 +122,33 @@ dependencies = [ "softfloat", ] +[[package]] +name = "bench-recover-k256" +version = "0.1.0" +dependencies = [ + "k256", + "picoalloc", + "polkavm-derive", +] + +[[package]] +name = "bench-recover-libsecp" +version = "0.1.0" +dependencies = [ + "libsecp256k1", + "picoalloc", + "polkavm-derive", +] + +[[package]] +name = "bench-sr25519" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "schnorrkel", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -101,6 +175,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cfg-if" version = "1.0.4" @@ -113,6 +193,12 @@ version = "0.1.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7925a77545f37a18e152a4aeaeb9a220309022067cafcfa2ecebe9ec55d36ccb" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.4.2" @@ -128,6 +214,24 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -147,10 +251,11 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -164,6 +269,25 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "digest" version = "0.10.7" @@ -171,7 +295,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -195,6 +335,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "ed25519-zebra" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775765289f7c6336c18d3d66127527820dd45ffd9eb3b6b8ee4708590e6c20f5" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "emumisc" version = "0.1.0" @@ -210,6 +383,16 @@ dependencies = [ "polkavm-derive", ] +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -224,6 +407,27 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", +] + +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand_core", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", ] [[package]] @@ -235,6 +439,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + [[package]] name = "keccak" version = "0.1.6" @@ -250,6 +475,63 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + [[package]] name = "mos6502" version = "0.1.0" @@ -282,6 +564,16 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7823ffd00d2b55ebe51750a19f47f2a33cb1f1d135f5cba893379b81c4d44856" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "polkavm-common" version = "0.37.0" @@ -332,6 +624,31 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -341,12 +658,63 @@ dependencies = [ "semver", ] +[[package]] +name = "schnorrkel" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9fcb6c2e176e86ec703e22560d99d65a5ee9056ae45a08e13e84ebf796296f" +dependencies = [ + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "sha2" version = "0.10.9" @@ -355,7 +723,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -364,7 +732,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -373,6 +741,10 @@ name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] [[package]] name = "simplealloc" @@ -384,6 +756,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "359a2aa2309eed307acc397678f7fbb43989db6ee417a11752248fc98451e696" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "subtle" version = "2.6.1" @@ -458,3 +840,23 @@ name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] diff --git a/guest-programs/Cargo.toml b/guest-programs/Cargo.toml index 256200c05..24242918c 100644 --- a/guest-programs/Cargo.toml +++ b/guest-programs/Cargo.toml @@ -19,6 +19,12 @@ members = [ "bench-minimal", "bench-memset", "bench-ed25519", + "bench-ed25519-zebra", + "bench-sr25519", + "bench-ecdsa-k256", + "bench-ecdsa-libsecp", + "bench-recover-k256", + "bench-recover-libsecp", "bench-hash", "bench-pinky", "bench-prime-sieve", diff --git a/guest-programs/bench-ecdsa-k256/Cargo.toml b/guest-programs/bench-ecdsa-k256/Cargo.toml new file mode 100644 index 000000000..06fe5e05a --- /dev/null +++ b/guest-programs/bench-ecdsa-k256/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bench-ecdsa-k256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_ecdsa_k256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-ecdsa-k256" +path = "src/main.rs" + +[dependencies] +# What sp_core::ecdsa uses in no_std runtimes. +k256 = { version = "0.13", default-features = false, features = ["ecdsa", "alloc"] } +blake2b_simd = { version = "1", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-ecdsa-k256/src/main.rs b/guest-programs/bench-ecdsa-k256/src/main.rs new file mode 100644 index 000000000..d6d067eff --- /dev/null +++ b/guest-programs/bench-ecdsa-k256/src/main.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +// secp256k1 arithmetic needs more than the default 8 KiB of stack. +#[cfg(target_env = "polkavm")] +polkavm_derive::min_stack_size!(128 * 1024); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// Classic ECDSA verification (blake2_256 of the message + `verify_prehash`) +// with `k256`, the crate sp_core::ecdsa uses in no_std runtimes. +// +// Note: `sp_io::crypto::ecdsa_verify` is actually implemented as +// recover-public-key-and-compare (see sp_core `verify_prehashed`) — that +// operation is measured by the `recover-k256` benchmark; this one measures +// classic verification for comparison. The std host side of sp_io uses the +// C `secp256k1` crate, which is not buildable for the PVM target. +// +#[path = "../../ecdsa-fixtures.rs"] +mod fixtures; +use fixtures::{MESSAGE, PUBLIC_KEY, SIGNATURE}; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + use k256::ecdsa::signature::hazmat::PrehashVerifier; + + let hash = blake2b_simd::Params::new().hash_length(32).hash(black_box(&MESSAGE)); + let key = k256::ecdsa::VerifyingKey::from_sec1_bytes(black_box(&PUBLIC_KEY)).unwrap(); + let signature = k256::ecdsa::Signature::from_slice(black_box(&SIGNATURE[..64])).unwrap(); + black_box(key.verify_prehash(hash.as_bytes(), &signature)).unwrap(); +} diff --git a/guest-programs/bench-ecdsa-libsecp/Cargo.toml b/guest-programs/bench-ecdsa-libsecp/Cargo.toml new file mode 100644 index 000000000..4195e99dd --- /dev/null +++ b/guest-programs/bench-ecdsa-libsecp/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bench-ecdsa-libsecp" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_ecdsa_libsecp" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-ecdsa-libsecp" +path = "src/main.rs" + +[dependencies] +# Pure-Rust port of the C libsecp256k1; what the deprecated sp_io v1 ecdsa +# host function used. +libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"] } +blake2b_simd = { version = "1", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-ecdsa-libsecp/src/main.rs b/guest-programs/bench-ecdsa-libsecp/src/main.rs new file mode 100644 index 000000000..2bdcd1bd6 --- /dev/null +++ b/guest-programs/bench-ecdsa-libsecp/src/main.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +// secp256k1 arithmetic needs more than the default 8 KiB of stack. +#[cfg(target_env = "polkavm")] +polkavm_derive::min_stack_size!(128 * 1024); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// Classic ECDSA verification (blake2_256 of the message + `verify`) with the +// pure-Rust `libsecp256k1` — same operation and same fixture as +// bench-ecdsa-k256, so the two implementations are directly comparable. +// The std host side of sp_io uses the C `secp256k1` crate, which is not +// buildable for the PVM target. + +#[path = "../../ecdsa-fixtures.rs"] +mod fixtures; +use fixtures::{MESSAGE, PUBLIC_KEY, SIGNATURE}; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + + let hash: [u8; 32] = blake2b_simd::Params::new() + .hash_length(32) + .hash(black_box(&MESSAGE)) + .as_bytes() + .try_into() + .unwrap(); + let message = libsecp256k1::Message::parse(&hash); + let signature = libsecp256k1::Signature::parse_standard_slice(black_box(&SIGNATURE[..64])).unwrap(); + let public = libsecp256k1::PublicKey::parse_compressed(black_box(&PUBLIC_KEY)).unwrap(); + assert!(black_box(libsecp256k1::verify(&message, &signature, &public))); +} diff --git a/guest-programs/bench-ed25519-zebra/Cargo.toml b/guest-programs/bench-ed25519-zebra/Cargo.toml new file mode 100644 index 000000000..ea70fb559 --- /dev/null +++ b/guest-programs/bench-ed25519-zebra/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-ed25519-zebra" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_ed25519_zebra" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-ed25519-zebra" +path = "src/main.rs" + +[dependencies] +ed25519-zebra = { version = "4", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-ed25519-zebra/src/main.rs b/guest-programs/bench-ed25519-zebra/src/main.rs new file mode 100644 index 000000000..83721024e --- /dev/null +++ b/guest-programs/bench-ed25519-zebra/src/main.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// `ed25519-zebra` is what `sp_core::ed25519` verifies with (ZIP-215 rules); +// the sibling `bench-ed25519` uses `ed25519-dalek` (strict RFC 8032). +// Both use the same RFC 8032 test vector below, so the two implementations +// are directly comparable. + +const PUBLIC_KEY: [u8; 32] = [ + 0xfc, 0x51, 0xcd, 0x8e, 0x62, 0x18, 0xa1, 0xa3, 0x8d, 0xa4, 0x7e, 0xd0, 0x02, 0x30, 0xf0, 0x58, + 0x08, 0x16, 0xed, 0x13, 0xba, 0x33, 0x03, 0xac, 0x5d, 0xeb, 0x91, 0x15, 0x48, 0x90, 0x80, 0x25, +]; + +const SIGNATURE: [u8; 64] = [ + 0x62, 0x91, 0xd6, 0x57, 0xde, 0xec, 0x24, 0x02, 0x48, 0x27, 0xe6, 0x9c, 0x3a, 0xbe, 0x01, 0xa3, + 0x0c, 0xe5, 0x48, 0xa2, 0x84, 0x74, 0x3a, 0x44, 0x5e, 0x36, 0x80, 0xd7, 0xdb, 0x5a, 0xc3, 0xac, + 0x18, 0xff, 0x9b, 0x53, 0x8d, 0x16, 0xf2, 0x90, 0xae, 0x67, 0xf7, 0x60, 0x98, 0x4d, 0xc6, 0x59, + 0x4a, 0x7c, 0x15, 0xe9, 0x71, 0x6e, 0xd2, 0x8d, 0xc0, 0x27, 0xbe, 0xce, 0xea, 0x1e, 0xc4, 0x0a, +]; + +const MESSAGE: [u8; 2] = [0xaf, 0x82]; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + + let key = ed25519_zebra::VerificationKey::try_from(black_box(PUBLIC_KEY)).unwrap(); + let signature = ed25519_zebra::Signature::from(black_box(SIGNATURE)); + black_box(key.verify(&signature, &MESSAGE)).unwrap(); +} diff --git a/guest-programs/bench-recover-k256/Cargo.toml b/guest-programs/bench-recover-k256/Cargo.toml new file mode 100644 index 000000000..c9e506b08 --- /dev/null +++ b/guest-programs/bench-recover-k256/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "bench-recover-k256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_recover_k256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-recover-k256" +path = "src/main.rs" + +[dependencies] +# What sp_core::ecdsa uses in no_std runtimes. +k256 = { version = "0.13", default-features = false, features = ["ecdsa", "alloc"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-recover-k256/src/main.rs b/guest-programs/bench-recover-k256/src/main.rs new file mode 100644 index 000000000..68d43b3fb --- /dev/null +++ b/guest-programs/bench-recover-k256/src/main.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +// secp256k1 arithmetic needs more than the default 8 KiB of stack. +#[cfg(target_env = "polkavm")] +polkavm_derive::min_stack_size!(128 * 1024); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// `sp_io::crypto::secp256k1_ecdsa_recover` semantics with `k256`: recover the +// public key from a 65-byte recoverable signature and a 32-byte prehash, then +// compare with the expected key. This recover-and-compare is also exactly how +// `sp_io::crypto::ecdsa_verify` is implemented (sp_core `verify_prehashed`). + +#[path = "../../ecdsa-fixtures.rs"] +mod fixtures; +use fixtures::{HASH, PUBLIC_KEY, SIGNATURE}; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + + let recovery_id = k256::ecdsa::RecoveryId::from_byte(black_box(SIGNATURE[64])).unwrap(); + let signature = k256::ecdsa::Signature::from_slice(black_box(&SIGNATURE[..64])).unwrap(); + let recovered = + k256::ecdsa::VerifyingKey::recover_from_prehash(black_box(&HASH), &signature, recovery_id) + .unwrap(); + assert_eq!(recovered.to_encoded_point(true).as_bytes(), &PUBLIC_KEY[..]); +} diff --git a/guest-programs/bench-recover-libsecp/Cargo.toml b/guest-programs/bench-recover-libsecp/Cargo.toml new file mode 100644 index 000000000..4bb080afa --- /dev/null +++ b/guest-programs/bench-recover-libsecp/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bench-recover-libsecp" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_recover_libsecp" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-recover-libsecp" +path = "src/main.rs" + +[dependencies] +# Pure-Rust port of the C libsecp256k1; what the deprecated sp_io v1 ecdsa +# host function used. +libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-recover-libsecp/src/main.rs b/guest-programs/bench-recover-libsecp/src/main.rs new file mode 100644 index 000000000..19ab45bb6 --- /dev/null +++ b/guest-programs/bench-recover-libsecp/src/main.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +// secp256k1 arithmetic needs more than the default 8 KiB of stack. +#[cfg(target_env = "polkavm")] +polkavm_derive::min_stack_size!(128 * 1024); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// `sp_io::crypto::secp256k1_ecdsa_recover` semantics with the pure-Rust +// `libsecp256k1` — same operation and same fixture as bench-recover-k256, +// so the two implementations are directly comparable. + +#[path = "../../ecdsa-fixtures.rs"] +mod fixtures; +use fixtures::{HASH, PUBLIC_KEY, SIGNATURE}; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + + let message = libsecp256k1::Message::parse(black_box(&HASH)); + let signature = libsecp256k1::Signature::parse_standard_slice(black_box(&SIGNATURE[..64])).unwrap(); + let recovery_id = libsecp256k1::RecoveryId::parse(black_box(SIGNATURE[64])).unwrap(); + let recovered = libsecp256k1::recover(&message, &signature, &recovery_id).unwrap(); + assert_eq!(recovered.serialize_compressed(), PUBLIC_KEY); +} diff --git a/guest-programs/bench-sr25519/Cargo.toml b/guest-programs/bench-sr25519/Cargo.toml new file mode 100644 index 000000000..6c39d6020 --- /dev/null +++ b/guest-programs/bench-sr25519/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "bench-sr25519" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_sr25519" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-sr25519" +path = "src/main.rs" + +[dependencies] +# Same feature selection as sp_core. +schnorrkel = { version = "0.11", default-features = false, features = ["preaudit_deprecated"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-sr25519/src/main.rs b/guest-programs/bench-sr25519/src/main.rs new file mode 100644 index 000000000..a178e0118 --- /dev/null +++ b/guest-programs/bench-sr25519/src/main.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); + +struct State; +define_benchmark! { + heap_size = 16 * 1024, + state = State, +} + +// Mirrors `sp_io::crypto::sr25519_verify` (version 2), i.e. `sp_core::sr25519`: +// schnorrkel `verify_simple` with the "substrate" signing context. Version 1 of +// the host function used `verify_deprecated` (schnorrkel's pre-audit format, +// enabled by the `preaudit_deprecated` feature) — not benchmarked separately. +// +// Fixture generated with: seed [1u8; 32], `MiniSecretKey::expand_to_keypair` +// with `ExpansionMode::Ed25519`, signed with context b"substrate" over MESSAGE. + +const SIGNING_CTX: &[u8] = b"substrate"; + +const PUBLIC_KEY: [u8; 32] = [ + 0x18, 0x9d, 0xac, 0x29, 0x29, 0x6d, 0x31, 0x81, 0x4d, 0xc8, 0xc5, 0x6c, 0xf3, 0xd3, 0x6a, 0x05, + 0x43, 0x37, 0x2b, 0xba, 0x75, 0x38, 0xfa, 0x32, 0x2a, 0x4a, 0xeb, 0xfe, 0xbc, 0x39, 0xe0, 0x56, +]; + +const SIGNATURE: [u8; 64] = [ + 0xb8, 0x8b, 0xbd, 0x69, 0xd3, 0x3b, 0xcd, 0xc7, 0xab, 0xd6, 0x87, 0xcb, 0x67, 0x52, 0x7d, 0xab, + 0x86, 0x76, 0x45, 0x43, 0x9a, 0x79, 0xd2, 0xd1, 0x79, 0x94, 0x74, 0x5b, 0x79, 0xed, 0x2a, 0x22, + 0x8a, 0x60, 0xe0, 0xf0, 0xc2, 0x4e, 0x5e, 0x98, 0x48, 0x34, 0xfd, 0x31, 0x7c, 0x3d, 0xcf, 0x19, + 0x47, 0x93, 0x6f, 0xf1, 0x59, 0x59, 0x0b, 0xfa, 0x92, 0x5e, 0x16, 0x2a, 0x75, 0x28, 0x56, 0x88, +]; + +const MESSAGE: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +]; + +fn benchmark_initialize(_state: &mut State) {} + +fn benchmark_run(_state: &mut State) { + use core::hint::black_box; + + let public = schnorrkel::PublicKey::from_bytes(black_box(&PUBLIC_KEY)).unwrap(); + let signature = schnorrkel::Signature::from_bytes(black_box(&SIGNATURE)).unwrap(); + black_box(public.verify_simple(SIGNING_CTX, &MESSAGE, &signature)).unwrap(); +} diff --git a/guest-programs/build-benchmarks.sh b/guest-programs/build-benchmarks.sh index c31c45969..702cc4e85 100755 --- a/guest-programs/build-benchmarks.sh +++ b/guest-programs/build-benchmarks.sh @@ -137,6 +137,12 @@ build_benchmark "bench-minimal" build_benchmark "bench-pinky" build_benchmark "bench-prime-sieve" build_benchmark "bench-ed25519" +build_benchmark "bench-ed25519-zebra" +build_benchmark "bench-sr25519" +build_benchmark "bench-ecdsa-k256" +build_benchmark "bench-ecdsa-libsecp" +build_benchmark "bench-recover-k256" +build_benchmark "bench-recover-libsecp" build_benchmark "bench-hash" if [ "${SOLANA_PLATFORM_TOOLS_DIR:-}" != "" ]; then diff --git a/guest-programs/ecdsa-fixtures.rs b/guest-programs/ecdsa-fixtures.rs new file mode 100644 index 000000000..19919a985 --- /dev/null +++ b/guest-programs/ecdsa-fixtures.rs @@ -0,0 +1,35 @@ +//! Shared secp256k1/ECDSA fixtures for the bench-ecdsa-* and bench-recover-* +//! benchmarks. Generated once host-side and cross-validated under both `k256` +//! and `libsecp256k1` (verify + recover): +//! +//! - signing key: k256, from seed `[2u8; 32]` +//! - `MESSAGE`: bytes `0..32` +//! - `HASH`: `blake2_256(MESSAGE)` +//! - `SIGNATURE`: `r || s || recovery_id` (65 bytes), signed over `HASH` +//! - `PUBLIC_KEY`: SEC1-compressed (33 bytes) + +#![allow(dead_code)] + +pub const PUBLIC_KEY: [u8; 33] = [ + 0x02, 0x4d, 0x4b, 0x6c, 0xd1, 0x36, 0x10, 0x32, 0xca, 0x9b, 0xd2, 0xae, 0xb9, 0xd9, 0x00, 0xaa, + 0x4d, 0x45, 0xd9, 0xea, 0xd8, 0x0a, 0xc9, 0x42, 0x33, 0x74, 0xc4, 0x51, 0xa7, 0x25, 0x4d, 0x07, + 0x66, +]; + +pub const SIGNATURE: [u8; 65] = [ + 0x6b, 0xb9, 0xb2, 0x74, 0x96, 0xcf, 0xe5, 0xf8, 0x7b, 0x15, 0xd8, 0xf2, 0xd4, 0x85, 0xb9, 0x72, + 0xa4, 0xa6, 0x95, 0x20, 0x88, 0xb8, 0x82, 0x91, 0x2c, 0x32, 0xc1, 0xa9, 0x13, 0x24, 0xdb, 0xc9, + 0x0f, 0xf3, 0xf8, 0x39, 0x0d, 0x31, 0xf0, 0xc5, 0x52, 0x7d, 0x44, 0xfe, 0xe3, 0x75, 0x0f, 0x47, + 0xe0, 0x79, 0xf5, 0xa6, 0xdf, 0x35, 0xd8, 0xc0, 0xa3, 0x43, 0x07, 0x3d, 0x62, 0x70, 0xf9, 0x6c, + 0x01, +]; + +pub const MESSAGE: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +]; + +pub const HASH: [u8; 32] = [ + 0xcb, 0x2f, 0x51, 0x60, 0xfc, 0x1f, 0x7e, 0x05, 0xa5, 0x5e, 0xf4, 0x9d, 0x34, 0x0b, 0x48, 0xda, + 0x2e, 0x5a, 0x78, 0x09, 0x9d, 0x53, 0x39, 0x33, 0x51, 0xcd, 0x57, 0x9d, 0xd4, 0x25, 0x03, 0xd6, +]; From 12db32de451b1e2fc73bac6a997e16ef13ef79d2 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:25:54 +0200 Subject: [PATCH 08/25] bench-ed25519: use builtins-mem --- guest-programs/bench-ed25519/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/guest-programs/bench-ed25519/Cargo.toml b/guest-programs/bench-ed25519/Cargo.toml index b67aa48fa..5cb24634b 100644 --- a/guest-programs/bench-ed25519/Cargo.toml +++ b/guest-programs/bench-ed25519/Cargo.toml @@ -29,5 +29,6 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } workspace = true [features] +default = ["builtins-mem"] # Link compiler_builtins' word-wise memcpy/memset builtins-mem = [] From b7b52c30d98a5f581ef3fc642de1f1816a07f8dd Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:59:41 +0200 Subject: [PATCH 09/25] process-results --- tools/benchtool/process-results.sh | 73 ++++++++++++++++++------------ 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/tools/benchtool/process-results.sh b/tools/benchtool/process-results.sh index 15720a627..980545d97 100755 --- a/tools/benchtool/process-results.sh +++ b/tools/benchtool/process-results.sh @@ -3,11 +3,16 @@ # Turns `benchtool bench-hash --csv` output into per-algorithm markdown tables. # # Usage: -# benchtool bench-hash --csv blake2b sha256 keccak256 > results.csv +# benchtool bench-hash --csv blake2_256 sha2_256 ... > results.csv # ./process-results.sh results.csv > result-table.md # -# The artifact whose name ends in `_simd` is used as the baseline for ratios; -# if none is present, the first artifact (in input order) is the baseline. +# Artifact roles: +# bench-hash -> pvm (the PVM blob) +# libbench_hash_simd -> native (built with -C target-cpu=native) +# libbench_hash -> portable (plain native build) +# The PVM column gets ratios against both native and portable; a +# native/portable ratio shows what -C target-cpu=native buys (or costs) on +# the host. Any other artifact gets its own column with a ratio vs native. # Checksums are cross-checked: rows of the same (algo, size) must agree. set -eu @@ -21,7 +26,9 @@ NF < 6 { next } if (!(artifact in artifact_seen)) { artifact_seen[artifact] = ++artifact_count artifacts[artifact_count] = artifact - if (artifact ~ /_simd$/) baseline = artifact + if (artifact ~ /_simd$/) { native = artifact } + else if (artifact ~ /^libbench/) { portable = artifact } + else if (pvm == "") { pvm = artifact } } if (!((algo, size) in row_seen)) { row_seen[algo, size] = 1 @@ -42,11 +49,17 @@ NF < 6 { next } } function fmt(ns) { + if (ns == "") return "-" if (ns < 1000) return sprintf("%.0f ns", ns) if (ns < 1000000) return sprintf("%.2f µs", ns / 1000) return sprintf("%.2f ms", ns / 1000000) } +function ratio(a, b) { + if (a == "" || b == "" || b == 0) return "-" + return sprintf("%.2fx", a / b) +} + function fmt_size(bytes) { if (bytes < 1024) return bytes " B" if (bytes < 1048576) return (bytes / 1024) " KiB" @@ -54,45 +67,49 @@ function fmt_size(bytes) { } END { - if (baseline == "") baseline = artifacts[1] - - # Reorder so the baseline column comes first. - ordered_count = 0 - ordered[++ordered_count] = baseline + # Other artifacts = everything that is not one of the three roles. + other_count = 0 for (i = 1; i <= artifact_count; i++) { - if (artifacts[i] != baseline) ordered[++ordered_count] = artifacts[i] + if (artifacts[i] != pvm && artifacts[i] != native && artifacts[i] != portable) { + others[++other_count] = artifacts[i] + } } - for (i = 1; i <= artifact_count; i++) artifacts[i] = ordered[i] for (a = 1; a <= algo_count; a++) { algo = algos[a] printf "## %s\n\n", algo printf "| size |" - for (i = 1; i <= artifact_count; i++) { - printf " %s |", artifacts[i] - if (artifacts[i] != baseline) printf " vs %s |", baseline - } + if (pvm != "") printf " pvm |" + if (native != "") printf " native |" + if (portable != "") printf " portable |" + if (pvm != "" && native != "") printf " pvm/native |" + if (pvm != "" && portable != "") printf " pvm/portable |" + if (native != "" && portable != "") printf " native/portable |" + for (i = 1; i <= other_count; i++) printf " %s | %s/native |", others[i], others[i] printf "\n|---:|" - for (i = 1; i <= artifact_count; i++) { - printf "---:|" - if (artifacts[i] != baseline) printf "---:|" - } + cols = (pvm != "") + (native != "") + (portable != "") \ + + (pvm != "" && native != "") + (pvm != "" && portable != "") \ + + (native != "" && portable != "") + 2 * other_count + for (i = 0; i < cols; i++) printf "---:|" printf "\n" n = split(sizes_for[algo], size_list, " ") for (s = 1; s <= n; s++) { size = size_list[s] + t_pvm = time[pvm, algo, size] + t_nat = time[native, algo, size] + t_por = time[portable, algo, size] printf "| %s |", fmt_size(size) - base_ns = time[baseline, algo, size] - for (i = 1; i <= artifact_count; i++) { - ns = time[artifacts[i], algo, size] - if (ns == "") { printf " - |"; if (artifacts[i] != baseline) printf " - |"; continue } - printf " %s |", fmt(ns) - if (artifacts[i] != baseline) { - if (base_ns > 0) printf " %.2fx |", ns / base_ns - else printf " - |" - } + if (pvm != "") printf " %s |", fmt(t_pvm) + if (native != "") printf " %s |", fmt(t_nat) + if (portable != "") printf " %s |", fmt(t_por) + if (pvm != "" && native != "") printf " %s |", ratio(t_pvm, t_nat) + if (pvm != "" && portable != "") printf " %s |", ratio(t_pvm, t_por) + if (native != "" && portable != "") printf " %s |", ratio(t_nat, t_por) + for (i = 1; i <= other_count; i++) { + t_o = time[others[i], algo, size] + printf " %s | %s |", fmt(t_o), ratio(t_o, t_nat) } printf "\n" } From e41d0ca6b85640c695d58af75f8e5c0d53d8687b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:51:52 +0200 Subject: [PATCH 10/25] naming cleanup --- guest-programs/bench-hash/Cargo.toml | 6 +++--- guest-programs/build-hash-native.sh | 18 +++++++++++++----- tools/benchtool/process-results.sh | 15 +++++++++++---- tools/benchtool/src/main.rs | 6 +++--- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/guest-programs/bench-hash/Cargo.toml b/guest-programs/bench-hash/Cargo.toml index 979d9eb9a..0f3b3579f 100644 --- a/guest-programs/bench-hash/Cargo.toml +++ b/guest-programs/bench-hash/Cargo.toml @@ -30,9 +30,9 @@ polkavm-derive = { path = "../../crates/polkavm-derive" } # RUSTFLAGS="-C target-cpu=native" cargo build \ # --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash # -# and copy the resulting libbench_hash.so aside (e.g. as libbench_hash_simd.so -# next to benchtool, where it is auto-discovered as "hash-simd") before it is -# overwritten by a regular build. +# and copy the resulting libbench_hash.so aside as libbench_hash_native.so +# (auto-discovered as "hash-native") before it is overwritten by a regular +# build — see build-hash-native.sh, which does exactly this. [features] default = ["builtins-mem"] diff --git a/guest-programs/build-hash-native.sh b/guest-programs/build-hash-native.sh index c0562061d..34549af0e 100755 --- a/guest-programs/build-hash-native.sh +++ b/guest-programs/build-hash-native.sh @@ -1,11 +1,19 @@ #!/bin/sh -# Builds the bench-hash native library in both variants: -# libbench_hash.so - portable implementations -# libbench_hash_simd.so - CPU features enabled at compile time (e.g. AVX2) +# Builds the bench-hash host library in both variants: +# libbench_hash.so - host portable: runs on any x86-64; crates may +# still runtime-dispatch to fast paths (e.g. sha2 +# uses SHA-NI via cpufeatures when available) +# libbench_hash_native.so - host native: -C target-cpu=native, all ISA +# extensions of the BUILD machine enabled +# unconditionally # # Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool's -# `bench-hash` subcommand auto-discovers them as "hash" and "hash-simd". +# `bench-hash` subcommand auto-discovers them as "hash" and "hash-native". +# +# WARNING: never copy libbench_hash_native.so between machines - it is +# compiled for the exact CPU it was built on and will crash (SIGILL) on CPUs +# lacking any of its instruction-set extensions. Always rebuild locally. set -ex cd "${0%/*}" @@ -13,7 +21,7 @@ cd "${0%/*}" out=target/x86_64-unknown-linux-gnu/release RUSTFLAGS="-C target-cpu=native" cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash -cp "$out/libbench_hash.so" "$out/libbench_hash_simd.so" +cp "$out/libbench_hash.so" "$out/libbench_hash_native.so" # Rebuild without the CPU features; overwrites libbench_hash.so with the portable variant. cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash diff --git a/tools/benchtool/process-results.sh b/tools/benchtool/process-results.sh index 980545d97..f17a6717a 100755 --- a/tools/benchtool/process-results.sh +++ b/tools/benchtool/process-results.sh @@ -7,9 +7,10 @@ # ./process-results.sh results.csv > result-table.md # # Artifact roles: -# bench-hash -> pvm (the PVM blob) -# libbench_hash_simd -> native (built with -C target-cpu=native) -# libbench_hash -> portable (plain native build) +# bench-hash -> pvm (the PVM blob) +# libbench_hash_native -> native (host native: -C target-cpu=native; +# "_simd" accepted for old CSVs) +# libbench_hash -> portable (host portable build) # The PVM column gets ratios against both native and portable; a # native/portable ratio shows what -C target-cpu=native buys (or costs) on # the host. Any other artifact gets its own column with a ratio vs native. @@ -26,7 +27,7 @@ NF < 6 { next } if (!(artifact in artifact_seen)) { artifact_seen[artifact] = ++artifact_count artifacts[artifact_count] = artifact - if (artifact ~ /_simd$/) { native = artifact } + if (artifact ~ /_(native|simd)$/) { native = artifact } else if (artifact ~ /^libbench/) { portable = artifact } else if (pvm == "") { pvm = artifact } } @@ -75,6 +76,12 @@ END { } } + print "*pvm* = PVM guest blob (recompiler, sync gas) · *native* = host native" + print "build (`-C target-cpu=native`, build machine only) · *portable* = host" + print "portable build (runs on any x86-64; crates may runtime-dispatch, e.g." + print "sha2 uses SHA-NI where available)" + print "" + for (a = 1; a <= algo_count; a++) { algo = algos[a] printf "## %s\n\n", algo diff --git a/tools/benchtool/src/main.rs b/tools/benchtool/src/main.rs index 744811891..818be33ba 100644 --- a/tools/benchtool/src/main.rs +++ b/tools/benchtool/src/main.rs @@ -470,7 +470,7 @@ enum Args { /// Benchmarks hash functions. /// /// Discovers the bench-hash artifacts (PVM blob and native libraries, - /// including variants like libbench_hash_simd.so) and measures their + /// including variants like libbench_hash_native.so) and measures their /// `benchmark_(len, times) -> u64` exports over a grid of input /// sizes, printing raw per-iteration times. Comparing artifacts (e.g. /// PVM vs native) is up to the caller. @@ -911,8 +911,8 @@ fn main() { // Discover artifacts like `benchmark` does. This picks up the standard // bench-hash artifacts from guest-programs/target as well as any variant - // there or in the current directory (e.g. a `libbench_hash_simd.so` is - // discovered as "hash-simd"). + // there or in the current directory (e.g. a `libbench_hash_native.so` is + // discovered as "hash-native"). let mut artifacts = Vec::new(); for benchmark in find_benchmarks().unwrap() { if benchmark.name != "hash" && !benchmark.name.starts_with("hash-") { From 0ee9d983cd212aa44caa9a3cff034ae14ee6c803 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:15:46 +0200 Subject: [PATCH 11/25] experiments: blake2 asm+compact implementation added --- guest-programs/bench-hash/src/asm_blake2b.rs | 55 + .../bench-hash/src/blake2b_compress.S | 2021 +++++++++++++++++ .../bench-hash/src/compact_blake2b.rs | 117 + guest-programs/bench-hash/src/main.rs | 21 + tools/benchtool/src/main.rs | 10 +- 5 files changed, 2222 insertions(+), 2 deletions(-) create mode 100644 guest-programs/bench-hash/src/asm_blake2b.rs create mode 100644 guest-programs/bench-hash/src/blake2b_compress.S create mode 100644 guest-programs/bench-hash/src/compact_blake2b.rs diff --git a/guest-programs/bench-hash/src/asm_blake2b.rs b/guest-programs/bench-hash/src/asm_blake2b.rs new file mode 100644 index 000000000..aa2d31a64 --- /dev/null +++ b/guest-programs/bench-hash/src/asm_blake2b.rs @@ -0,0 +1,55 @@ +//! blake2b-256 with a hand-scheduled RISC-V assembly compression function +//! (`blake2b_compress.S`, generated). +//! +//! Rationale: the recompiled output of the portable Rust implementation is +//! issue-bound — LLVM's register allocation for the 13-register rv64e target +//! spills far more than necessary and its 3-operand code forces the +//! recompiler to emit register-copy `mov`s. The assembly version pins the +//! state's a/b rows (v0..v7) in registers, streams the c/d rows through a +//! 64-byte stack frame, uses strictly two-operand form (no recompiler movs), +//! and bakes the message schedule into constant load offsets. +//! +//! On non-riscv64 targets (host builds, riscv32) this falls back to the +//! compact Rust implementation so the export exists everywhere and +//! checksums stay comparable. + +#[cfg(target_arch = "riscv64")] +core::arch::global_asm!(include_str!("blake2b_compress.S")); + +#[cfg(target_arch = "riscv64")] +extern "C" { + fn blake2b_compress_asm(h: *mut u64, block: *const u8, t: u64, iv: *const u64, last: u64); +} + +#[cfg(target_arch = "riscv64")] +pub fn blake2b_256(input: &[u8]) -> [u8; 32] { + use crate::compact_blake2b::IV; + + let mut h = IV; + h[0] ^= 0x0101_0000 ^ 32; + + let mut t: u64 = 0; + let mut offset = 0; + while input.len() - offset > 128 { + t += 128; + unsafe { blake2b_compress_asm(h.as_mut_ptr(), input.as_ptr().add(offset), t, IV.as_ptr(), 0) }; + offset += 128; + } + + let rem = &input[offset..]; + let mut block = [0u8; 128]; + block[..rem.len()].copy_from_slice(rem); + t += rem.len() as u64; + unsafe { blake2b_compress_asm(h.as_mut_ptr(), block.as_ptr(), t, IV.as_ptr(), 1) }; + + let mut out = [0u8; 32]; + for i in 0..4 { + out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); + } + out +} + +#[cfg(not(target_arch = "riscv64"))] +pub fn blake2b_256(input: &[u8]) -> [u8; 32] { + crate::compact_blake2b::blake2b_256(input) +} diff --git a/guest-programs/bench-hash/src/blake2b_compress.S b/guest-programs/bench-hash/src/blake2b_compress.S new file mode 100644 index 000000000..af4d88b12 --- /dev/null +++ b/guest-programs/bench-hash/src/blake2b_compress.S @@ -0,0 +1,2021 @@ +// Hand-scheduled blake2b compression for PVM (rv64e + zbb). +// Generated - see the report / bench-hash sources for context. +// fn blake2b_compress_asm(h: *mut u64 [a0], block: *const u8 [a1], +// t: u64 [a2], iv: *const u64 [a3], last: u64 [a4]) +.section .text.blake2b_compress_asm,"ax" +.globl blake2b_compress_asm +blake2b_compress_asm: + addi sp, sp, -96 + sd ra, 72(sp) + sd s0, 80(sp) + sd s1, 88(sp) + sd a0, 64(sp) + // v8..v15 = IV[0..7] (+ counter / finalization flag) + ld t1, 0(a3) + sd t1, 0(sp) + ld t1, 8(a3) + sd t1, 8(sp) + ld t1, 16(a3) + sd t1, 16(sp) + ld t1, 24(a3) + sd t1, 24(sp) + ld t1, 32(a3) + xor t1, t1, a2 + sd t1, 32(sp) + ld t1, 40(a3) + sd t1, 40(sp) + ld t1, 48(a3) + neg t2, a4 + xor t1, t1, t2 + sd t1, 48(sp) + ld t1, 56(a3) + sd t1, 56(sp) + mv t0, a1 + // v0..v7 = h[0..7] (a0 loaded last - it is the source pointer) + ld s1, 56(a0) + ld s0, 48(a0) + ld a5, 40(a0) + ld a4, 32(a0) + ld a3, 24(a0) + ld a2, 16(a0) + ld a1, 8(a0) + ld a0, 0(a0) + // round 0 + ld t1, 0(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 8(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 16(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 24(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 32(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 80(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 96(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 1 + ld t1, 112(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 32(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 64(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 120(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 104(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 96(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 24(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 2 + ld t1, 88(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 64(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 96(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 0(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 40(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 120(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 80(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 112(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 24(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 48(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 56(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 8(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 72(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 32(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 3 + ld t1, 56(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 72(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 24(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 8(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 104(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 96(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 112(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 16(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 48(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 40(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 80(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 32(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 0(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 120(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 64(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 4 + ld t1, 72(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 0(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 40(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 56(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 16(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 32(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 80(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 120(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 8(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 88(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 96(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 64(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 24(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 104(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 5 + ld t1, 16(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 96(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 48(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 80(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 64(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 24(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 32(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 104(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 56(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 120(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 112(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 72(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 6 + ld t1, 96(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 40(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 8(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 120(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 112(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 104(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 32(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 80(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 0(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 56(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 48(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 24(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 72(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 16(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 88(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 7 + ld t1, 104(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 88(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 56(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 112(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 96(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 8(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 24(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 72(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 0(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 120(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 32(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 64(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 16(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 8 + ld t1, 48(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 112(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 88(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 24(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 0(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 64(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 96(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 16(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 104(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 56(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 8(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 32(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 80(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 40(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 9 + ld t1, 80(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 16(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 64(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 32(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 56(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 48(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 8(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 40(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 120(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 88(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 112(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 24(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 96(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 104(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 0(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 10 + ld t1, 0(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 8(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 16(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 24(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 32(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 80(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 96(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 11 + ld t1, 112(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 32(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 64(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 120(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 104(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 96(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 24(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // h[i] ^= v[i] ^ v[i+8] + ld t0, 64(sp) + ld t1, 0(t0) + xor t1, t1, a0 + ld t2, 0(sp) + xor t1, t1, t2 + sd t1, 0(t0) + ld t1, 8(t0) + xor t1, t1, a1 + ld t2, 8(sp) + xor t1, t1, t2 + sd t1, 8(t0) + ld t1, 16(t0) + xor t1, t1, a2 + ld t2, 16(sp) + xor t1, t1, t2 + sd t1, 16(t0) + ld t1, 24(t0) + xor t1, t1, a3 + ld t2, 24(sp) + xor t1, t1, t2 + sd t1, 24(t0) + ld t1, 32(t0) + xor t1, t1, a4 + ld t2, 32(sp) + xor t1, t1, t2 + sd t1, 32(t0) + ld t1, 40(t0) + xor t1, t1, a5 + ld t2, 40(sp) + xor t1, t1, t2 + sd t1, 40(t0) + ld t1, 48(t0) + xor t1, t1, s0 + ld t2, 48(sp) + xor t1, t1, t2 + sd t1, 48(t0) + ld t1, 56(t0) + xor t1, t1, s1 + ld t2, 56(sp) + xor t1, t1, t2 + sd t1, 56(t0) + ld ra, 72(sp) + ld s0, 80(sp) + ld s1, 88(sp) + addi sp, sp, 96 + ret diff --git a/guest-programs/bench-hash/src/compact_blake2b.rs b/guest-programs/bench-hash/src/compact_blake2b.rs new file mode 100644 index 000000000..a50a8150e --- /dev/null +++ b/guest-programs/bench-hash/src/compact_blake2b.rs @@ -0,0 +1,117 @@ +//! A code-size-compact blake2b-256: a single round body in a loop, instead +//! of the fully-unrolled form used by `blake2b_simd` (and RustCrypto +//! `blake2`). +//! +//! Rationale: on PVM the recompiled unrolled compression carries ~2x the +//! instructions of native (spills + 3-op to 2-op translation), overflowing +//! the host CPU's uop cache and capping the front-end at legacy-decoder +//! width. Rolling the rounds into a loop keeps the hot code cache-resident +//! while the state still lives in registers; the costs are the loop control +//! and SIGMA-indexed (instead of constant-offset) message loads. +//! +//! The loop bound is laundered through `black_box` so the compiler cannot +//! unroll it back. + +pub(crate) const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +const SIGMA: [[u8; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +#[inline(always)] +fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +#[inline(never)] +fn compress(h: &mut [u64; 8], block: &[u8; 128], t: u128, last: bool) { + let mut m = [0u64; 16]; + for (i, chunk) in block.chunks_exact(8).enumerate() { + m[i] = u64::from_le_bytes(chunk.try_into().unwrap()); + } + + let mut v = [0u64; 16]; + v[..8].copy_from_slice(h); + v[8..].copy_from_slice(&IV); + v[12] ^= t as u64; + v[13] ^= (t >> 64) as u64; + if last { + v[14] = !v[14]; + } + + // One round body; the opaque bound keeps it a real loop. The sigma row + // is selected with a rotating index (no modulo — an opaque `r % 10` + // would compile to a real division per round), and the message indices + // are masked so the loads are provably in-bounds (no bounds checks). + let mut s_idx = 0; + for _ in 0..core::hint::black_box(12usize) { + let s = &SIGMA[s_idx]; + s_idx += 1; + if s_idx == 10 { + s_idx = 0; + } + g(&mut v, 0, 4, 8, 12, m[s[0] as usize & 15], m[s[1] as usize & 15]); + g(&mut v, 1, 5, 9, 13, m[s[2] as usize & 15], m[s[3] as usize & 15]); + g(&mut v, 2, 6, 10, 14, m[s[4] as usize & 15], m[s[5] as usize & 15]); + g(&mut v, 3, 7, 11, 15, m[s[6] as usize & 15], m[s[7] as usize & 15]); + g(&mut v, 0, 5, 10, 15, m[s[8] as usize & 15], m[s[9] as usize & 15]); + g(&mut v, 1, 6, 11, 12, m[s[10] as usize & 15], m[s[11] as usize & 15]); + g(&mut v, 2, 7, 8, 13, m[s[12] as usize & 15], m[s[13] as usize & 15]); + g(&mut v, 3, 4, 9, 14, m[s[14] as usize & 15], m[s[15] as usize & 15]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} + +pub fn blake2b_256(input: &[u8]) -> [u8; 32] { + let mut h = IV; + h[0] ^= 0x0101_0000 ^ 32; // digest_length = 32, key = 0, fanout = 1, depth = 1 + + let mut t: u128 = 0; + let mut offset = 0; + while input.len() - offset > 128 { + let block: &[u8; 128] = input[offset..offset + 128].try_into().unwrap(); + t += 128; + compress(&mut h, block, t, false); + offset += 128; + } + + let rem = &input[offset..]; + let mut block = [0u8; 128]; + block[..rem.len()].copy_from_slice(rem); + t += rem.len() as u128; + compress(&mut h, &block, t, true); + + let mut out = [0u8; 32]; + for i in 0..4 { + out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); + } + out +} diff --git a/guest-programs/bench-hash/src/main.rs b/guest-programs/bench-hash/src/main.rs index 123585d4b..b1ca3b408 100644 --- a/guest-programs/bench-hash/src/main.rs +++ b/guest-programs/bench-hash/src/main.rs @@ -48,6 +48,25 @@ fn blake2_256_once(input: &[u8], out: &mut Output) -> usize { blake2b_once(input, out, 32) } +mod compact_blake2b; + +// Same function as `blake2_256`, but computed by the code-size-compact +// rounds-loop implementation; its checksums must match `blake2_256`'s. +fn blake2_256_compact_once(input: &[u8], out: &mut Output) -> usize { + out[..32].copy_from_slice(&compact_blake2b::blake2b_256(input)); + 32 +} + +mod asm_blake2b; + +// Same function as `blake2_256`, but with a hand-scheduled RISC-V assembly +// compression function; its checksums must match `blake2_256`'s. On non-PVM +// targets it falls back to the compact implementation. +fn blake2_256_asm_once(input: &[u8], out: &mut Output) -> usize { + out[..32].copy_from_slice(&asm_blake2b::blake2b_256(input)); + 32 +} + fn keccak_256_once(input: &[u8], out: &mut Output) -> usize { use sha3::Digest; let mut hasher = sha3::Keccak256::new(); @@ -117,6 +136,8 @@ macro_rules! export_hash_benchmark { // Named after the `sp_io::hashing` host functions they mirror. export_hash_benchmark!(benchmark_blake2_128, blake2_128_once); export_hash_benchmark!(benchmark_blake2_256, blake2_256_once); +export_hash_benchmark!(benchmark_blake2_256_compact, blake2_256_compact_once); +export_hash_benchmark!(benchmark_blake2_256_asm, blake2_256_asm_once); export_hash_benchmark!(benchmark_keccak_256, keccak_256_once); export_hash_benchmark!(benchmark_keccak_512, keccak_512_once); export_hash_benchmark!(benchmark_sha2_256, sha2_256_once); diff --git a/tools/benchtool/src/main.rs b/tools/benchtool/src/main.rs index 818be33ba..ca62a613f 100644 --- a/tools/benchtool/src/main.rs +++ b/tools/benchtool/src/main.rs @@ -489,6 +489,10 @@ enum Args { #[clap(long)] csv: bool, + /// Run with ASLR enabled. + #[clap(long)] + aslr: bool, + /// Hash algorithm names; each is resolved as a `benchmark_` export. #[clap(required = true)] algos: Vec, @@ -853,8 +857,10 @@ fn main() { } } } - Args::BenchHash { sizes, total_bytes, csv, algos } => { - disable_aslr(); + Args::BenchHash { sizes, total_bytes, csv, aslr, algos } => { + if !aslr { + disable_aslr(); + } // bench-hash's input buffer size; larger sizes would silently clamp // in the guest and mislabel the row. From 59e3293308a08165c02b7ac29d8b364c68dbd0fc Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:34:01 +0200 Subject: [PATCH 12/25] experiments: blake2 ... --- guest-programs/bench-hash/src/asm_blake2b.rs | 22 ++- .../bench-hash/src/blake2b_compress.S | 186 +++++++++++------- 2 files changed, 130 insertions(+), 78 deletions(-) diff --git a/guest-programs/bench-hash/src/asm_blake2b.rs b/guest-programs/bench-hash/src/asm_blake2b.rs index aa2d31a64..d2b88999a 100644 --- a/guest-programs/bench-hash/src/asm_blake2b.rs +++ b/guest-programs/bench-hash/src/asm_blake2b.rs @@ -18,7 +18,10 @@ core::arch::global_asm!(include_str!("blake2b_compress.S")); #[cfg(target_arch = "riscv64")] extern "C" { - fn blake2b_compress_asm(h: *mut u64, block: *const u8, t: u64, iv: *const u64, last: u64); + /// Processes `nblocks` consecutive 128-byte blocks; the byte counter for + /// block k is `t_base + 128*k` (wrapping). `last != 0` finalizes every + /// block in the batch, so callers pass `nblocks = 1` for the last block. + fn blake2b_compress_many(h: *mut u64, data: *const u8, nblocks: u64, t_base: u64, iv: *const u64, last: u64); } #[cfg(target_arch = "riscv64")] @@ -28,19 +31,18 @@ pub fn blake2b_256(input: &[u8]) -> [u8; 32] { let mut h = IV; h[0] ^= 0x0101_0000 ^ 32; - let mut t: u64 = 0; - let mut offset = 0; - while input.len() - offset > 128 { - t += 128; - unsafe { blake2b_compress_asm(h.as_mut_ptr(), input.as_ptr().add(offset), t, IV.as_ptr(), 0) }; - offset += 128; + // All full blocks except a full last block (the last block is always + // finalized separately below). + let full = input.len().saturating_sub(1) / 128; + if full > 0 { + unsafe { blake2b_compress_many(h.as_mut_ptr(), input.as_ptr(), full as u64, 0, IV.as_ptr(), 0) }; } - let rem = &input[offset..]; + let rem = &input[full * 128..]; let mut block = [0u8; 128]; block[..rem.len()].copy_from_slice(rem); - t += rem.len() as u64; - unsafe { blake2b_compress_asm(h.as_mut_ptr(), block.as_ptr(), t, IV.as_ptr(), 1) }; + let t_base = (input.len() as u64).wrapping_sub(128); + unsafe { blake2b_compress_many(h.as_mut_ptr(), block.as_ptr(), 1, t_base, IV.as_ptr(), 1) }; let mut out = [0u8; 32]; for i in 0..4 { diff --git a/guest-programs/bench-hash/src/blake2b_compress.S b/guest-programs/bench-hash/src/blake2b_compress.S index af4d88b12..b9779d5db 100644 --- a/guest-programs/bench-hash/src/blake2b_compress.S +++ b/guest-programs/bench-hash/src/blake2b_compress.S @@ -1,35 +1,46 @@ -// Hand-scheduled blake2b compression for PVM (rv64e + zbb). +// Hand-scheduled blake2b compression loop for PVM (rv64e + zbb). // Generated - see the report / bench-hash sources for context. -// fn blake2b_compress_asm(h: *mut u64 [a0], block: *const u8 [a1], -// t: u64 [a2], iv: *const u64 [a3], last: u64 [a4]) -.section .text.blake2b_compress_asm,"ax" -.globl blake2b_compress_asm -blake2b_compress_asm: - addi sp, sp, -96 - sd ra, 72(sp) - sd s0, 80(sp) - sd s1, 88(sp) - sd a0, 64(sp) - // v8..v15 = IV[0..7] (+ counter / finalization flag) - ld t1, 0(a3) - sd t1, 0(sp) - ld t1, 8(a3) - sd t1, 8(sp) - ld t1, 16(a3) - sd t1, 16(sp) - ld t1, 24(a3) - sd t1, 24(sp) - ld t1, 32(a3) - xor t1, t1, a2 - sd t1, 32(sp) - ld t1, 40(a3) - sd t1, 40(sp) - ld t1, 48(a3) - neg t2, a4 +// fn blake2b_compress_many(h: *mut u64 [a0], data: *const u8 [a1], +// nblocks: u64 [a2], t_base: u64 [a3], +// iv: *const u64 [a4], last: u64 [a5]) +// +// Processes `nblocks` consecutive 128-byte blocks; the byte counter for +// block k is t_base + 128*k (wrapping, so a short/empty last block passes +// t_base = total_len - 128). If `last` != 0 every block in the batch is +// finalized (callers pass nblocks = 1 for the last block). +// h is kept in the pinned registers across blocks; the a/b rows of the +// state (v0..v7) never touch memory inside a block. +.section .text.blake2b_compress_many,"ax" +.globl blake2b_compress_many +blake2b_compress_many: + addi sp, sp, -240 + sd ra, 216(sp) + sd s0, 224(sp) + sd s1, 232(sp) + sd a0, 208(sp) + sd a3, 192(sp) + sd a2, 200(sp) + // IV copy; IV[6] pre-masked with the finalization flag + ld t1, 0(a4) + sd t1, 64(sp) + ld t1, 8(a4) + sd t1, 72(sp) + ld t1, 16(a4) + sd t1, 80(sp) + ld t1, 24(a4) + sd t1, 88(sp) + ld t1, 32(a4) + sd t1, 96(sp) + ld t1, 40(a4) + sd t1, 104(sp) + ld t1, 48(a4) + sd t1, 112(sp) + ld t1, 56(a4) + sd t1, 120(sp) + ld t1, 112(sp) + neg t2, a5 xor t1, t1, t2 - sd t1, 48(sp) - ld t1, 56(a3) - sd t1, 56(sp) + sd t1, 112(sp) mv t0, a1 // v0..v7 = h[0..7] (a0 loaded last - it is the source pointer) ld s1, 56(a0) @@ -40,6 +51,37 @@ blake2b_compress_asm: ld a2, 16(a0) ld a1, 8(a0) ld a0, 0(a0) +.Lblock: + // save h_old; the pinned registers become the working state v0..v7 + sd a0, 128(sp) + sd a1, 136(sp) + sd a2, 144(sp) + sd a3, 152(sp) + sd a4, 160(sp) + sd a5, 168(sp) + sd s0, 176(sp) + sd s1, 184(sp) + // t += 128; v8..v15 = IV copy, v12 ^= t + ld t1, 192(sp) + addi t1, t1, 128 + sd t1, 192(sp) + ld t2, 96(sp) + xor t2, t2, t1 + sd t2, 32(sp) + ld t1, 64(sp) + sd t1, 0(sp) + ld t1, 72(sp) + sd t1, 8(sp) + ld t1, 80(sp) + sd t1, 16(sp) + ld t1, 88(sp) + sd t1, 24(sp) + ld t1, 104(sp) + sd t1, 40(sp) + ld t1, 112(sp) + sd t1, 48(sp) + ld t1, 120(sp) + sd t1, 56(sp) // round 0 ld t1, 0(t0) add a0, a0, a4 @@ -1972,50 +2014,58 @@ blake2b_compress_asm: sd t1, 8(sp) xor a4, a4, t1 rori a4, a4, 63 - // h[i] ^= v[i] ^ v[i+8] - ld t0, 64(sp) - ld t1, 0(t0) - xor t1, t1, a0 + // h ^= v[0..7] ^ v[8..15]; the result stays pinned for the next block + ld t1, 128(sp) ld t2, 0(sp) - xor t1, t1, t2 - sd t1, 0(t0) - ld t1, 8(t0) - xor t1, t1, a1 + xor a0, a0, t1 + xor a0, a0, t2 + ld t1, 136(sp) ld t2, 8(sp) - xor t1, t1, t2 - sd t1, 8(t0) - ld t1, 16(t0) - xor t1, t1, a2 + xor a1, a1, t1 + xor a1, a1, t2 + ld t1, 144(sp) ld t2, 16(sp) - xor t1, t1, t2 - sd t1, 16(t0) - ld t1, 24(t0) - xor t1, t1, a3 + xor a2, a2, t1 + xor a2, a2, t2 + ld t1, 152(sp) ld t2, 24(sp) - xor t1, t1, t2 - sd t1, 24(t0) - ld t1, 32(t0) - xor t1, t1, a4 + xor a3, a3, t1 + xor a3, a3, t2 + ld t1, 160(sp) ld t2, 32(sp) - xor t1, t1, t2 - sd t1, 32(t0) - ld t1, 40(t0) - xor t1, t1, a5 + xor a4, a4, t1 + xor a4, a4, t2 + ld t1, 168(sp) ld t2, 40(sp) - xor t1, t1, t2 - sd t1, 40(t0) - ld t1, 48(t0) - xor t1, t1, s0 + xor a5, a5, t1 + xor a5, a5, t2 + ld t1, 176(sp) ld t2, 48(sp) - xor t1, t1, t2 - sd t1, 48(t0) - ld t1, 56(t0) - xor t1, t1, s1 + xor s0, s0, t1 + xor s0, s0, t2 + ld t1, 184(sp) ld t2, 56(sp) - xor t1, t1, t2 - sd t1, 56(t0) - ld ra, 72(sp) - ld s0, 80(sp) - ld s1, 88(sp) - addi sp, sp, 96 + xor s1, s1, t1 + xor s1, s1, t2 + // next block + addi t0, t0, 128 + ld t1, 200(sp) + addi t1, t1, -1 + sd t1, 200(sp) + beqz t1, .Ldone + j .Lblock +.Ldone: + ld t1, 208(sp) + sd a0, 0(t1) + sd a1, 8(t1) + sd a2, 16(t1) + sd a3, 24(t1) + sd a4, 32(t1) + sd a5, 40(t1) + sd s0, 48(t1) + sd s1, 56(t1) + ld ra, 216(sp) + ld s0, 224(sp) + ld s1, 232(sp) + addi sp, sp, 240 ret From 1d575fbb3714cd3e623c0739ca596978bf58fb3f Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:13:11 +0200 Subject: [PATCH 13/25] benchtool: size and set-size added --- tools/benchtool/src/backend.rs | 33 ++ tools/benchtool/src/backend/backend_native.rs | 16 + .../benchtool/src/backend/backend_polkavm.rs | 16 + tools/benchtool/src/backend/backend_wasmer.rs | 23 +- tools/benchtool/src/backend/backend_wasmi.rs | 19 +- .../benchtool/src/backend/backend_wasmtime.rs | 19 +- tools/benchtool/src/main.rs | 306 ++++++------------ 7 files changed, 220 insertions(+), 212 deletions(-) diff --git a/tools/benchtool/src/backend.rs b/tools/benchtool/src/backend.rs index feee7f0eb..1e9ba794c 100644 --- a/tools/benchtool/src/backend.rs +++ b/tools/benchtool/src/backend.rs @@ -24,6 +24,18 @@ pub trait Backend: Copy + Clone { fn spawn(&self, engine: &mut Self::Engine, module: &Self::Module) -> Self::Instance; fn initialize(&self, instance: &mut Self::Instance); fn run(&self, instance: &mut Self::Instance); + + /// Calls the benchmark's optional `benchmark_set_size` export. + /// + /// Returns `false` when the benchmark (or this backend) doesn't support it. + fn set_size(&self, _instance: &mut Self::Instance, _size: u64) -> bool { + false + } + + /// Whether this backend implements [`Backend::set_size`] at all. + fn supports_set_size(&self) -> bool { + false + } fn pid(&self, _instance: &Self::Instance) -> Option { None } @@ -197,6 +209,18 @@ macro_rules! define_backends { } } + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + match self { + $( + #[cfg($($cfg)*)] + Self::$backend => { + let AnyInstance::$backend(instance) = instance else { unreachable!() }; + self::$module::$struct($($ctor_args)*).set_size(instance, size) + }, + )+ + } + } + fn pid(&self, instance: &Self::Instance) -> Option { match self { $( @@ -209,6 +233,15 @@ macro_rules! define_backends { } } + fn supports_set_size(&self) -> bool { + match self { + $( + #[cfg($($cfg)*)] + Self::$backend => self::$module::$struct($($ctor_args)*).supports_set_size(), + )+ + } + } + fn is_slow(&self) -> bool { match self { $( diff --git a/tools/benchtool/src/backend/backend_native.rs b/tools/benchtool/src/backend/backend_native.rs index 384df323e..669af94f8 100644 --- a/tools/benchtool/src/backend/backend_native.rs +++ b/tools/benchtool/src/backend/backend_native.rs @@ -5,6 +5,7 @@ pub struct Native(); pub struct NativeInstance { initialize: libloading::Symbol<'static, unsafe extern "C" fn()>, run: libloading::Symbol<'static, unsafe extern "C" fn()>, + set_size: Option>, _library: libloading::Library, } @@ -35,9 +36,12 @@ impl Backend for Native { let initialize: libloading::Symbol = core::mem::transmute(initialize); let run: libloading::Symbol = library.get(b"run").unwrap(); let run: libloading::Symbol = core::mem::transmute(run); + let set_size: Option> = library.get(b"benchmark_set_size").ok(); + let set_size: Option> = core::mem::transmute(set_size); NativeInstance { initialize, run, + set_size, _library: library, } } @@ -54,4 +58,16 @@ impl Backend for Native { (instance.run)(); } } + + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + let Some(ref set_size) = instance.set_size else { return false }; + unsafe { + set_size(size); + } + true + } + + fn supports_set_size(&self) -> bool { + true + } } diff --git a/tools/benchtool/src/backend/backend_polkavm.rs b/tools/benchtool/src/backend/backend_polkavm.rs index 9e70f7387..63ee1421a 100644 --- a/tools/benchtool/src/backend/backend_polkavm.rs +++ b/tools/benchtool/src/backend/backend_polkavm.rs @@ -17,6 +17,7 @@ pub struct PolkaVM( pub struct Instance { ext_initialize: polkavm::ProgramCounter, ext_run: polkavm::ProgramCounter, + ext_set_size: Option, instance: polkavm::Instance<()>, } @@ -84,9 +85,14 @@ impl Backend for PolkaVM { let instance = instance_pre.instantiate().unwrap(); let ext_initialize = module.exports().find(|export| export == "initialize").unwrap().program_counter(); let ext_run = module.exports().find(|export| export == "run").unwrap().program_counter(); + let ext_set_size = module + .exports() + .find(|export| export == "benchmark_set_size") + .map(|export| export.program_counter()); Instance { ext_initialize, ext_run, + ext_set_size, instance, } } @@ -103,6 +109,16 @@ impl Backend for PolkaVM { instance.instance.call_typed(&mut (), instance.ext_run, ()).unwrap(); } + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + let Some(ext_set_size) = instance.ext_set_size else { return false }; + instance.instance.call_typed(&mut (), ext_set_size, (size,)).unwrap(); + true + } + + fn supports_set_size(&self) -> bool { + true + } + fn pid(&self, instance: &Self::Instance) -> Option { instance.instance.pid() } diff --git a/tools/benchtool/src/backend/backend_wasmer.rs b/tools/benchtool/src/backend/backend_wasmer.rs index cea776926..ec754d212 100644 --- a/tools/benchtool/src/backend/backend_wasmer.rs +++ b/tools/benchtool/src/backend/backend_wasmer.rs @@ -8,6 +8,7 @@ pub struct WasmerInstance { store: wasmer::Store, initialize: wasmer::TypedFunction<(), ()>, run: wasmer::TypedFunction<(), ()>, + set_size: Option>, } #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] @@ -40,7 +41,17 @@ impl Backend for Wasmer { let instance = wasmer::Instance::new(&mut store, module, &import_object).unwrap(); let initialize: wasmer::TypedFunction<(), ()> = instance.exports.get_function("initialize").unwrap().typed(&store).unwrap(); let run: wasmer::TypedFunction<(), ()> = instance.exports.get_function("run").unwrap().typed(&store).unwrap(); - WasmerInstance { store, initialize, run } + let set_size: Option> = instance + .exports + .get_function("benchmark_set_size") + .ok() + .map(|function| function.typed(&store).unwrap()); + WasmerInstance { + store, + initialize, + run, + set_size, + } } fn initialize(&self, instance: &mut Self::Instance) { @@ -51,6 +62,16 @@ impl Backend for Wasmer { instance.run.call(&mut instance.store).unwrap(); } + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + let Some(ref set_size) = instance.set_size else { return false }; + set_size.call(&mut instance.store, size).unwrap(); + true + } + + fn supports_set_size(&self) -> bool { + true + } + fn is_compiled(&self) -> bool { true } diff --git a/tools/benchtool/src/backend/backend_wasmi.rs b/tools/benchtool/src/backend/backend_wasmi.rs index fd04b304b..b7219c1f9 100644 --- a/tools/benchtool/src/backend/backend_wasmi.rs +++ b/tools/benchtool/src/backend/backend_wasmi.rs @@ -38,6 +38,7 @@ pub struct WasmiInstance { store: wasmi::Store<()>, initialize: wasmi::TypedFunc<(), ()>, run: wasmi::TypedFunc<(), ()>, + set_size: Option>, } impl Backend for Wasmi { @@ -85,7 +86,13 @@ impl Backend for Wasmi { let instance = instance.ensure_no_start(&mut store).unwrap(); let initialize = instance.get_typed_func::<(), ()>(&mut store, "initialize").unwrap(); let run = instance.get_typed_func::<(), ()>(&mut store, "run").unwrap(); - WasmiInstance { store, initialize, run } + let set_size = instance.get_typed_func::(&mut store, "benchmark_set_size").ok(); + WasmiInstance { + store, + initialize, + run, + set_size, + } } fn initialize(&self, instance: &mut Self::Instance) { @@ -96,6 +103,16 @@ impl Backend for Wasmi { instance.run.call(&mut instance.store, ()).unwrap(); } + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + let Some(ref set_size) = instance.set_size else { return false }; + set_size.call(&mut instance.store, size).unwrap(); + true + } + + fn supports_set_size(&self) -> bool { + true + } + fn is_compiled(&self) -> bool { true } diff --git a/tools/benchtool/src/backend/backend_wasmtime.rs b/tools/benchtool/src/backend/backend_wasmtime.rs index 09434bf0d..372cd92a2 100644 --- a/tools/benchtool/src/backend/backend_wasmtime.rs +++ b/tools/benchtool/src/backend/backend_wasmtime.rs @@ -16,6 +16,7 @@ pub struct WasmtimeInstance { store: wasmtime::Store<()>, initialize: wasmtime::TypedFunc<(), ()>, run: wasmtime::TypedFunc<(), ()>, + set_size: Option>, } #[cfg(all(feature = "wasmtime", any(target_arch = "x86_64", target_arch = "aarch64")))] @@ -73,7 +74,13 @@ impl Backend for Wasmtime { let instance = wasmtime::Instance::new(&mut store, module, &[]).unwrap(); let initialize = instance.get_typed_func::<(), ()>(&mut store, "initialize").unwrap(); let run = instance.get_typed_func::<(), ()>(&mut store, "run").unwrap(); - WasmtimeInstance { store, initialize, run } + let set_size = instance.get_typed_func::(&mut store, "benchmark_set_size").ok(); + WasmtimeInstance { + store, + initialize, + run, + set_size, + } } fn initialize(&self, instance: &mut Self::Instance) { @@ -84,6 +91,16 @@ impl Backend for Wasmtime { instance.run.call(&mut instance.store, ()).unwrap(); } + fn set_size(&self, instance: &mut Self::Instance, size: u64) -> bool { + let Some(ref set_size) = instance.set_size else { return false }; + set_size.call(&mut instance.store, size).unwrap(); + true + } + + fn supports_set_size(&self) -> bool { + true + } + fn is_compiled(&self) -> bool { true } diff --git a/tools/benchtool/src/main.rs b/tools/benchtool/src/main.rs index ca62a613f..884dd82b1 100644 --- a/tools/benchtool/src/main.rs +++ b/tools/benchtool/src/main.rs @@ -30,6 +30,7 @@ fn benchmark_execution( inner_count: u32, backend: T, path: &Path, + size: Option, ) -> core::time::Duration { let mut total_elapsed = core::time::Duration::new(0, 0); let mut engine = engine_cache @@ -40,6 +41,9 @@ fn benchmark_execution( for _ in 0..outer_count { let mut instance = backend.spawn(&mut engine, &module); backend.initialize(&mut instance); + if let Some(size) = size { + assert!(backend.set_size(&mut instance, size)); + } let start = std::time::Instant::now(); for _ in 0..inner_count { backend.run(&mut instance); @@ -66,7 +70,13 @@ fn benchmark_compilation(engine_cache: &mut Option, count elapsed } -fn benchmark_oneshot(engine_cache: &mut Option, count: u64, backend: T, path: &Path) -> core::time::Duration { +fn benchmark_oneshot( + engine_cache: &mut Option, + count: u64, + backend: T, + path: &Path, + size: Option, +) -> core::time::Duration { let mut engine = engine_cache .take() .unwrap_or_else(|| backend.create(CreateArgs { is_compile_only: false })); @@ -75,6 +85,9 @@ fn benchmark_oneshot(engine_cache: &mut Option, count: u6 let module = backend.compile(&mut engine, &blob); let mut instance = backend.spawn(&mut engine, &module); backend.initialize(&mut instance); + if let Some(size) = size { + assert!(backend.set_size(&mut instance, size)); + } backend.run(&mut instance); } @@ -83,6 +96,9 @@ fn benchmark_oneshot(engine_cache: &mut Option, count: u6 let module = backend.compile(&mut engine, &blob); let mut instance = backend.spawn(&mut engine, &module); backend.initialize(&mut instance); + if let Some(size) = size { + assert!(backend.set_size(&mut instance, size)); + } backend.run(&mut instance); } let elapsed = start.elapsed(); @@ -102,7 +118,7 @@ fn criterion_main(c: &mut Criterion, benches: &[Benchmark]) { for bench in variants { for backend in bench.kind.matching_backends() { group.bench_function(backend.name(), |b| { - b.iter_custom(|count| benchmark_execution(&mut None, count, FAST_INNER_COUNT, backend, &bench.path)); + b.iter_custom(|count| benchmark_execution(&mut None, count, FAST_INNER_COUNT, backend, &bench.path, None)); }); } } @@ -130,7 +146,7 @@ fn criterion_main(c: &mut Criterion, benches: &[Benchmark]) { for bench in variants { for backend in bench.kind.matching_backends() { group.bench_function(backend.name(), |b| { - b.iter_custom(|count| benchmark_oneshot(&mut None, count, backend, &bench.path)); + b.iter_custom(|count| benchmark_oneshot(&mut None, count, backend, &bench.path, None)); }); } } @@ -257,6 +273,16 @@ fn find_benchmarks() -> Result, std::io::Error> { Ok(output) } +/// Whether the benchmark's artifact exports `benchmark_set_size`. +/// +/// The export's name appears verbatim in every artifact format we produce +/// (.polkavm export table, ELF dynsym, wasm export section), so a plain byte +/// scan is sufficient. +fn benchmark_supports_set_size(benchmark: &Benchmark) -> bool { + const SET_SIZE: &[u8] = b"benchmark_set_size"; + std::fs::read(&benchmark.path).is_ok_and(|bytes| bytes.windows(SET_SIZE.len()).any(|window| window == SET_SIZE)) +} + #[derive(Copy, Clone)] enum BenchVariant { Runtime, @@ -439,6 +465,11 @@ enum Args { #[clap(long)] aslr: bool, + /// Comma-separated input sizes, in bytes; passed to benchmarks that + /// export `benchmark_set_size` (others run unparameterized as usual). + #[clap(long)] + size: Option, + filter: Option, }, @@ -457,6 +488,11 @@ enum Args { #[clap(long, short = 'i')] iteration_limit: Option, + /// Input size, in bytes; passed to the benchmark's + /// `benchmark_set_size` export (must be supported). + #[clap(long)] + size: Option, + /// The `perf` subcommand to run. command: String, @@ -467,37 +503,6 @@ enum Args { /// Benchmarks PolkaVM's memset. BenchMemset, - /// Benchmarks hash functions. - /// - /// Discovers the bench-hash artifacts (PVM blob and native libraries, - /// including variants like libbench_hash_native.so) and measures their - /// `benchmark_(len, times) -> u64` exports over a grid of input - /// sizes, printing raw per-iteration times. Comparing artifacts (e.g. - /// PVM vs native) is up to the caller. - BenchHash { - /// Comma-separated input sizes, in bytes. - #[clap(long, default_value = "32,128,512,4096,65536,1048576")] - sizes: String, - - /// Target total bytes hashed per row; iteration count is derived as - /// max(1, total_bytes / size). - #[clap(long, default_value_t = 32 * 1024 * 1024)] - total_bytes: u64, - - /// Output CSV (artifact,algo,size,ns_per_iter,times,checksum) instead of - /// human-readable rows. - #[clap(long)] - csv: bool, - - /// Run with ASLR enabled. - #[clap(long)] - aslr: bool, - - /// Hash algorithm names; each is resolved as a `benchmark_` export. - #[clap(required = true)] - algos: Vec, - }, - /// Benchmarks ecalli overhead. BenchEcalli { #[clap(long)] @@ -578,27 +583,58 @@ fn main() { filter, forever, aslr, + size, } => { if !aslr { disable_aslr(); } + let sizes: Option> = size.map(|size| { + size.split(',') + .map(|value| value.trim().parse().expect("invalid --size entry")) + .collect() + }); + let mut list = Vec::new(); let benches = find_benchmarks().unwrap(); for bench in &benches { + // Sizes apply only to benchmarks that export `benchmark_set_size`; + // everything else runs unparameterized, exactly as without --size. + let bench_sizes: Vec> = match sizes { + Some(ref sizes) if benchmark_supports_set_size(bench) => sizes.iter().copied().map(Some).collect(), + _ => vec![None], + }; + for backend in bench.kind.matching_backends() { for variant in [BenchVariant::Runtime, BenchVariant::Compilation, BenchVariant::Oneshot] { if matches!(variant, BenchVariant::Compilation) && !backend.is_compiled() { continue; } - let name = format!("{}/{}/{}", variant.name(), bench.name, backend.name()); - if let Some(ref filter) = filter { - if !name.contains(filter) { + // Compilation is size-independent; run it once. + let variant_sizes: &[Option] = if matches!(variant, BenchVariant::Compilation) { + &[None] + } else { + &bench_sizes + }; + + for &bench_size in variant_sizes { + // Skip sized entries on backends that cannot call the export. + if bench_size.is_some() && !backend.supports_set_size() { continue; } + + let name = match bench_size { + Some(bench_size) => format!("{}/{}/{}/{}", variant.name(), bench.name, backend.name(), bench_size), + None => format!("{}/{}/{}", variant.name(), bench.name, backend.name()), + }; + if let Some(ref filter) = filter { + if !name.contains(filter) { + continue; + } + } + list.push((name, variant, bench, backend, bench_size)); } - list.push((name, variant, bench, backend)); } } } @@ -624,7 +660,7 @@ fn main() { loop { let is_initial_run = stats_for_bench.is_empty(); - for (nth_bench, &(ref name, variant, bench, backend)) in list.iter().enumerate() { + for (nth_bench, &(ref name, variant, bench, backend, bench_size)) in list.iter().enumerate() { use std::io::Write; let _ = write!(&mut std::io::stdout(), "{name}: ..."); let _ = std::io::stdout().flush(); @@ -636,8 +672,14 @@ fn main() { } else { (iteration_limit.unwrap_or(12), FAST_INNER_COUNT) }; - benchmark_execution(&mut engine_cache[nth_bench], outer_count, inner_count, backend, &bench.path) - / outer_count as u32 + benchmark_execution( + &mut engine_cache[nth_bench], + outer_count, + inner_count, + backend, + &bench.path, + bench_size, + ) / outer_count as u32 } BenchVariant::Compilation => { let count = if cfg!(miri) { 1 } else { iteration_limit.unwrap_or(128) }; @@ -645,7 +687,7 @@ fn main() { } BenchVariant::Oneshot => { let count = iteration_limit.unwrap_or(10); - benchmark_oneshot(&mut engine_cache[nth_bench], count, backend, &bench.path) / count as u32 + benchmark_oneshot(&mut engine_cache[nth_bench], count, backend, &bench.path, bench_size) / count as u32 } }; @@ -713,6 +755,7 @@ fn main() { benchmark, mut time_limit, iteration_limit, + size, command, perf_args, } => { @@ -733,6 +776,12 @@ fn main() { let module = backend.compile(&mut engine, &blob); let mut instance = backend.spawn(&mut engine, &module); backend.initialize(&mut instance); + if let Some(size) = size { + assert!( + backend.set_size(&mut instance, size), + "this benchmark/backend does not support --size" + ); + } let pid = backend.pid(&instance); (instance, pid) }, @@ -762,6 +811,12 @@ fn main() { let module = backend.compile(engine, blob); let mut instance = backend.spawn(engine, &module); backend.initialize(&mut instance); + if let Some(size) = size { + assert!( + backend.set_size(&mut instance, size), + "this benchmark/backend does not support --size" + ); + } backend.run(&mut instance); }, ), @@ -857,173 +912,6 @@ fn main() { } } } - Args::BenchHash { sizes, total_bytes, csv, aslr, algos } => { - if !aslr { - disable_aslr(); - } - - // bench-hash's input buffer size; larger sizes would silently clamp - // in the guest and mislabel the row. - const MAX_LEN: u64 = 1024 * 1024; - let sizes: Vec = sizes - .split(',') - .map(|s| s.trim().parse().expect("invalid --sizes entry")) - .inspect(|&size| { - assert!( - (1..=MAX_LEN).contains(&size), - "--sizes entries must be in 1..={MAX_LEN} (got {size})" - ); - }) - .collect(); - - struct Row<'a> { - algo: &'a str, - size: u64, - times: u64, - elapsed: Duration, - checksum: u64, - } - - fn print_row(csv: bool, artifact: &str, row: Row) { - if csv { - println!( - "{artifact},{algo},{size},{ns_per_iter:.2},{times},{checksum:016x}", - algo = row.algo, - size = row.size, - ns_per_iter = row.elapsed.as_nanos() as f64 / row.times as f64, - times = row.times, - checksum = row.checksum, - ); - } else { - println!( - "{artifact} {algo:<12} {size:>8}: {time_per_iter}/iter (x{times}) checksum={checksum:016x}", - algo = row.algo, - size = row.size, - time_per_iter = format_time_with_div(row.elapsed, row.times as u32), - times = row.times, - checksum = row.checksum, - ); - } - } - - if csv { - println!("artifact,algo,size,ns_per_iter,times,checksum"); - } - - enum Artifact { - Pvm(PathBuf), - Native(PathBuf), - } - - // Discover artifacts like `benchmark` does. This picks up the standard - // bench-hash artifacts from guest-programs/target as well as any variant - // there or in the current directory (e.g. a `libbench_hash_native.so` is - // discovered as "hash-native"). - let mut artifacts = Vec::new(); - for benchmark in find_benchmarks().unwrap() { - if benchmark.name != "hash" && !benchmark.name.starts_with("hash-") { - continue; - } - match benchmark.kind { - BenchmarkKind::PolkaVM64 => artifacts.push(Artifact::Pvm(benchmark.path)), - BenchmarkKind::Native => artifacts.push(Artifact::Native(benchmark.path)), - _ => {} - } - } - if artifacts.is_empty() { - eprintln!( - "no bench-hash artifacts found; build them with \ - guest-programs/build-benchmarks.sh and guest-programs/build-hash-native.sh" - ); - std::process::exit(1); - } - - for artifact in artifacts { - match artifact { - Artifact::Pvm(path) => { - let config = polkavm::Config::from_env().unwrap(); - let engine = polkavm::Engine::new(&config).unwrap(); - let raw_blob = std::fs::read(&path).unwrap(); - let blob = polkavm::ProgramBlob::parse(raw_blob.into()).unwrap(); - let mut module_config = polkavm::ModuleConfig::default(); - module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync)); - let module = polkavm::Module::from_blob(&engine, &module_config, blob).unwrap(); - let linker = polkavm::Linker::<()>::new(); - let instance_pre = linker.instantiate_pre(&module).unwrap(); - let mut instance = instance_pre.instantiate().unwrap(); - let artifact = path.file_stem().unwrap().to_string_lossy().into_owned(); - - instance.set_gas(polkavm::Gas::MAX); - instance.call_typed(&mut (), "initialize", ()).unwrap(); - - for algo in &algos { - let export = format!("benchmark_{algo}"); - for &size in &sizes { - let times = (total_bytes / size).max(1); - let mut measure = || -> (Duration, u64) { - // Reset the input buffer so every measurement is - // self-contained and checksums are comparable - // across runs regardless of row order. - instance.set_gas(polkavm::Gas::MAX); - instance.call_typed(&mut (), "initialize", ()).unwrap(); - instance.set_gas(polkavm::Gas::MAX); - let timestamp = std::time::Instant::now(); - let checksum: u64 = instance - .call_typed_and_get_result(&mut (), &*export, (size, times)) - .unwrap(); - (timestamp.elapsed(), checksum) - }; - measure(); // Warmup. - let (elapsed, checksum) = measure(); - print_row(csv, &artifact, Row { algo, size, times, elapsed, checksum }); - } - } - } - Artifact::Native(path) => { - #[cfg(feature = "native")] - { - // A bare filename would be looked up in the system library - // search path by dlopen, not in the current directory. - let path = path.canonicalize().unwrap_or_else(|error| { - eprintln!("failed to resolve {path:?}: {error}"); - std::process::exit(1); - }); - let library = unsafe { libloading::Library::new(&path) }.unwrap(); - let artifact = path.file_stem().unwrap().to_string_lossy().into_owned(); - - let initialize = unsafe { - library.get::(b"initialize").unwrap() - }; - - for algo in &algos { - let export = format!("benchmark_{algo}"); - let func: libloading::Symbol u64> = - unsafe { library.get(export.as_bytes()) }.unwrap(); - for &size in &sizes { - let times = (total_bytes / size).max(1); - let mut measure = || -> (Duration, u64) { - // See the PVM path: reset state for comparable checksums. - unsafe { initialize() }; - let timestamp = std::time::Instant::now(); - let checksum = unsafe { func(size, times) }; - (timestamp.elapsed(), checksum) - }; - measure(); // Warmup. - let (elapsed, checksum) = measure(); - print_row(csv, &artifact, Row { algo, size, times, elapsed, checksum }); - } - } - } - #[cfg(not(feature = "native"))] - { - let _ = path; - eprintln!("native libraries require benchtool to be built with the 'native' feature"); - std::process::exit(1); - } - } - } - } - } Args::BenchEcalli { interpreter } => { use polkavm_common::program::{asm, InstructionSetKind, ProgramBlob}; let mut builder = polkavm_common::writer::ProgramBlobBuilder::new(InstructionSetKind::JamV1); From 05721e9f5e7e7b350086f2864b949513cf94a5e2 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:14:49 +0200 Subject: [PATCH 14/25] bench-hash removed --- guest-programs/bench-hash/Cargo.toml | 42 - guest-programs/bench-hash/src/asm_blake2b.rs | 57 - .../bench-hash/src/blake2b_compress.S | 2071 ----------------- .../bench-hash/src/compact_blake2b.rs | 117 - guest-programs/bench-hash/src/main.rs | 146 -- 5 files changed, 2433 deletions(-) delete mode 100644 guest-programs/bench-hash/Cargo.toml delete mode 100644 guest-programs/bench-hash/src/asm_blake2b.rs delete mode 100644 guest-programs/bench-hash/src/blake2b_compress.S delete mode 100644 guest-programs/bench-hash/src/compact_blake2b.rs delete mode 100644 guest-programs/bench-hash/src/main.rs diff --git a/guest-programs/bench-hash/Cargo.toml b/guest-programs/bench-hash/Cargo.toml deleted file mode 100644 index 0f3b3579f..000000000 --- a/guest-programs/bench-hash/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "bench-hash" -version = "0.1.0" -edition = "2021" -publish = false - -[lib] -name = "bench_hash" -path = "src/main.rs" -crate-type = ["cdylib"] - -[[bin]] -name = "bench-hash" -path = "src/main.rs" - -[dependencies] -blake2b_simd = { version = "1", default-features = false } -sha2 = { version = "0.10", default-features = false } -sha3 = { version = "0.10", default-features = false } -twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } -picoalloc = { workspace = true } - -[target.'cfg(target_env = "polkavm")'.dependencies] -polkavm-derive = { path = "../../crates/polkavm-derive" } - -# To build a SIMD-accelerated native library for comparison, enable the CPU -# features at compile time (runtime dispatch would require std, which -# conflicts with this crate being no_std): -# -# RUSTFLAGS="-C target-cpu=native" cargo build \ -# --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash -# -# and copy the resulting libbench_hash.so aside as libbench_hash_native.so -# (auto-discovered as "hash-native") before it is overwritten by a regular -# build — see build-hash-native.sh, which does exactly this. - -[features] -default = ["builtins-mem"] -builtins-mem = [] - -[lints] -workspace = true diff --git a/guest-programs/bench-hash/src/asm_blake2b.rs b/guest-programs/bench-hash/src/asm_blake2b.rs deleted file mode 100644 index d2b88999a..000000000 --- a/guest-programs/bench-hash/src/asm_blake2b.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! blake2b-256 with a hand-scheduled RISC-V assembly compression function -//! (`blake2b_compress.S`, generated). -//! -//! Rationale: the recompiled output of the portable Rust implementation is -//! issue-bound — LLVM's register allocation for the 13-register rv64e target -//! spills far more than necessary and its 3-operand code forces the -//! recompiler to emit register-copy `mov`s. The assembly version pins the -//! state's a/b rows (v0..v7) in registers, streams the c/d rows through a -//! 64-byte stack frame, uses strictly two-operand form (no recompiler movs), -//! and bakes the message schedule into constant load offsets. -//! -//! On non-riscv64 targets (host builds, riscv32) this falls back to the -//! compact Rust implementation so the export exists everywhere and -//! checksums stay comparable. - -#[cfg(target_arch = "riscv64")] -core::arch::global_asm!(include_str!("blake2b_compress.S")); - -#[cfg(target_arch = "riscv64")] -extern "C" { - /// Processes `nblocks` consecutive 128-byte blocks; the byte counter for - /// block k is `t_base + 128*k` (wrapping). `last != 0` finalizes every - /// block in the batch, so callers pass `nblocks = 1` for the last block. - fn blake2b_compress_many(h: *mut u64, data: *const u8, nblocks: u64, t_base: u64, iv: *const u64, last: u64); -} - -#[cfg(target_arch = "riscv64")] -pub fn blake2b_256(input: &[u8]) -> [u8; 32] { - use crate::compact_blake2b::IV; - - let mut h = IV; - h[0] ^= 0x0101_0000 ^ 32; - - // All full blocks except a full last block (the last block is always - // finalized separately below). - let full = input.len().saturating_sub(1) / 128; - if full > 0 { - unsafe { blake2b_compress_many(h.as_mut_ptr(), input.as_ptr(), full as u64, 0, IV.as_ptr(), 0) }; - } - - let rem = &input[full * 128..]; - let mut block = [0u8; 128]; - block[..rem.len()].copy_from_slice(rem); - let t_base = (input.len() as u64).wrapping_sub(128); - unsafe { blake2b_compress_many(h.as_mut_ptr(), block.as_ptr(), 1, t_base, IV.as_ptr(), 1) }; - - let mut out = [0u8; 32]; - for i in 0..4 { - out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); - } - out -} - -#[cfg(not(target_arch = "riscv64"))] -pub fn blake2b_256(input: &[u8]) -> [u8; 32] { - crate::compact_blake2b::blake2b_256(input) -} diff --git a/guest-programs/bench-hash/src/blake2b_compress.S b/guest-programs/bench-hash/src/blake2b_compress.S deleted file mode 100644 index b9779d5db..000000000 --- a/guest-programs/bench-hash/src/blake2b_compress.S +++ /dev/null @@ -1,2071 +0,0 @@ -// Hand-scheduled blake2b compression loop for PVM (rv64e + zbb). -// Generated - see the report / bench-hash sources for context. -// fn blake2b_compress_many(h: *mut u64 [a0], data: *const u8 [a1], -// nblocks: u64 [a2], t_base: u64 [a3], -// iv: *const u64 [a4], last: u64 [a5]) -// -// Processes `nblocks` consecutive 128-byte blocks; the byte counter for -// block k is t_base + 128*k (wrapping, so a short/empty last block passes -// t_base = total_len - 128). If `last` != 0 every block in the batch is -// finalized (callers pass nblocks = 1 for the last block). -// h is kept in the pinned registers across blocks; the a/b rows of the -// state (v0..v7) never touch memory inside a block. -.section .text.blake2b_compress_many,"ax" -.globl blake2b_compress_many -blake2b_compress_many: - addi sp, sp, -240 - sd ra, 216(sp) - sd s0, 224(sp) - sd s1, 232(sp) - sd a0, 208(sp) - sd a3, 192(sp) - sd a2, 200(sp) - // IV copy; IV[6] pre-masked with the finalization flag - ld t1, 0(a4) - sd t1, 64(sp) - ld t1, 8(a4) - sd t1, 72(sp) - ld t1, 16(a4) - sd t1, 80(sp) - ld t1, 24(a4) - sd t1, 88(sp) - ld t1, 32(a4) - sd t1, 96(sp) - ld t1, 40(a4) - sd t1, 104(sp) - ld t1, 48(a4) - sd t1, 112(sp) - ld t1, 56(a4) - sd t1, 120(sp) - ld t1, 112(sp) - neg t2, a5 - xor t1, t1, t2 - sd t1, 112(sp) - mv t0, a1 - // v0..v7 = h[0..7] (a0 loaded last - it is the source pointer) - ld s1, 56(a0) - ld s0, 48(a0) - ld a5, 40(a0) - ld a4, 32(a0) - ld a3, 24(a0) - ld a2, 16(a0) - ld a1, 8(a0) - ld a0, 0(a0) -.Lblock: - // save h_old; the pinned registers become the working state v0..v7 - sd a0, 128(sp) - sd a1, 136(sp) - sd a2, 144(sp) - sd a3, 152(sp) - sd a4, 160(sp) - sd a5, 168(sp) - sd s0, 176(sp) - sd s1, 184(sp) - // t += 128; v8..v15 = IV copy, v12 ^= t - ld t1, 192(sp) - addi t1, t1, 128 - sd t1, 192(sp) - ld t2, 96(sp) - xor t2, t2, t1 - sd t2, 32(sp) - ld t1, 64(sp) - sd t1, 0(sp) - ld t1, 72(sp) - sd t1, 8(sp) - ld t1, 80(sp) - sd t1, 16(sp) - ld t1, 88(sp) - sd t1, 24(sp) - ld t1, 104(sp) - sd t1, 40(sp) - ld t1, 112(sp) - sd t1, 48(sp) - ld t1, 120(sp) - sd t1, 56(sp) - // round 0 - ld t1, 0(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 8(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 16(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 24(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 32(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 40(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 48(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 56(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 64(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 72(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 80(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 88(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 96(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 104(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 112(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 120(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 1 - ld t1, 112(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 80(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 32(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 64(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 72(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 120(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 104(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 48(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 8(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 96(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 0(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 16(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 88(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 56(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 40(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 24(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 2 - ld t1, 88(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 64(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 96(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 0(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 40(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 16(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 120(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 104(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 80(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 112(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 24(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 48(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 56(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 8(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 72(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 32(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 3 - ld t1, 56(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 72(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 24(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 8(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 104(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 96(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 88(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 112(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 16(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 48(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 40(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 80(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 32(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 0(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 120(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 64(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 4 - ld t1, 72(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 0(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 40(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 56(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 16(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 32(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 80(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 120(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 112(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 8(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 88(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 96(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 48(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 64(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 24(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 104(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 5 - ld t1, 16(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 96(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 48(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 80(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 0(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 88(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 64(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 24(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 32(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 104(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 56(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 40(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 120(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 112(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 8(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 72(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 6 - ld t1, 96(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 40(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 8(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 120(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 112(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 104(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 32(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 80(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 0(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 56(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 48(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 24(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 72(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 16(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 64(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 88(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 7 - ld t1, 104(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 88(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 56(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 112(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 96(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 8(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 24(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 72(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 40(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 0(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 120(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 32(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 64(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 48(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 16(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 80(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 8 - ld t1, 48(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 120(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 112(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 72(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 88(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 24(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 0(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 64(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 96(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 16(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 104(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 56(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 8(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 32(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 80(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 40(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 9 - ld t1, 80(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 16(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 64(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 32(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 56(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 48(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 8(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 40(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 120(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 88(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 72(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 112(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 24(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 96(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 104(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 0(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 10 - ld t1, 0(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 8(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 16(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 24(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 32(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 40(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 48(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 56(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 64(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 72(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 80(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 88(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 96(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 104(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 112(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 120(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // round 11 - ld t1, 112(t0) - add a0, a0, a4 - add a0, a0, t1 - ld t2, 32(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 80(t0) - add a0, a0, a4 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor a4, a4, t1 - rori a4, a4, 63 - ld t1, 32(t0) - add a1, a1, a5 - add a1, a1, t1 - ld t2, 40(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 64(t0) - add a1, a1, a5 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 72(t0) - add a2, a2, s0 - add a2, a2, t1 - ld t2, 48(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 120(t0) - add a2, a2, s0 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 104(t0) - add a3, a3, s1 - add a3, a3, t1 - ld t2, 56(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 48(t0) - add a3, a3, s1 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 8(t0) - add a0, a0, a5 - add a0, a0, t1 - ld t2, 56(sp) - xor t2, t2, a0 - rori t2, t2, 32 - ld t1, 16(sp) - add t1, t1, t2 - xor a5, a5, t1 - rori a5, a5, 24 - ld ra, 96(t0) - add a0, a0, a5 - add a0, a0, ra - xor t2, t2, a0 - rori t2, t2, 16 - sd t2, 56(sp) - add t1, t1, t2 - sd t1, 16(sp) - xor a5, a5, t1 - rori a5, a5, 63 - ld t1, 0(t0) - add a1, a1, s0 - add a1, a1, t1 - ld t2, 32(sp) - xor t2, t2, a1 - rori t2, t2, 32 - ld t1, 24(sp) - add t1, t1, t2 - xor s0, s0, t1 - rori s0, s0, 24 - ld ra, 16(t0) - add a1, a1, s0 - add a1, a1, ra - xor t2, t2, a1 - rori t2, t2, 16 - sd t2, 32(sp) - add t1, t1, t2 - sd t1, 24(sp) - xor s0, s0, t1 - rori s0, s0, 63 - ld t1, 88(t0) - add a2, a2, s1 - add a2, a2, t1 - ld t2, 40(sp) - xor t2, t2, a2 - rori t2, t2, 32 - ld t1, 0(sp) - add t1, t1, t2 - xor s1, s1, t1 - rori s1, s1, 24 - ld ra, 56(t0) - add a2, a2, s1 - add a2, a2, ra - xor t2, t2, a2 - rori t2, t2, 16 - sd t2, 40(sp) - add t1, t1, t2 - sd t1, 0(sp) - xor s1, s1, t1 - rori s1, s1, 63 - ld t1, 40(t0) - add a3, a3, a4 - add a3, a3, t1 - ld t2, 48(sp) - xor t2, t2, a3 - rori t2, t2, 32 - ld t1, 8(sp) - add t1, t1, t2 - xor a4, a4, t1 - rori a4, a4, 24 - ld ra, 24(t0) - add a3, a3, a4 - add a3, a3, ra - xor t2, t2, a3 - rori t2, t2, 16 - sd t2, 48(sp) - add t1, t1, t2 - sd t1, 8(sp) - xor a4, a4, t1 - rori a4, a4, 63 - // h ^= v[0..7] ^ v[8..15]; the result stays pinned for the next block - ld t1, 128(sp) - ld t2, 0(sp) - xor a0, a0, t1 - xor a0, a0, t2 - ld t1, 136(sp) - ld t2, 8(sp) - xor a1, a1, t1 - xor a1, a1, t2 - ld t1, 144(sp) - ld t2, 16(sp) - xor a2, a2, t1 - xor a2, a2, t2 - ld t1, 152(sp) - ld t2, 24(sp) - xor a3, a3, t1 - xor a3, a3, t2 - ld t1, 160(sp) - ld t2, 32(sp) - xor a4, a4, t1 - xor a4, a4, t2 - ld t1, 168(sp) - ld t2, 40(sp) - xor a5, a5, t1 - xor a5, a5, t2 - ld t1, 176(sp) - ld t2, 48(sp) - xor s0, s0, t1 - xor s0, s0, t2 - ld t1, 184(sp) - ld t2, 56(sp) - xor s1, s1, t1 - xor s1, s1, t2 - // next block - addi t0, t0, 128 - ld t1, 200(sp) - addi t1, t1, -1 - sd t1, 200(sp) - beqz t1, .Ldone - j .Lblock -.Ldone: - ld t1, 208(sp) - sd a0, 0(t1) - sd a1, 8(t1) - sd a2, 16(t1) - sd a3, 24(t1) - sd a4, 32(t1) - sd a5, 40(t1) - sd s0, 48(t1) - sd s1, 56(t1) - ld ra, 216(sp) - ld s0, 224(sp) - ld s1, 232(sp) - addi sp, sp, 240 - ret diff --git a/guest-programs/bench-hash/src/compact_blake2b.rs b/guest-programs/bench-hash/src/compact_blake2b.rs deleted file mode 100644 index a50a8150e..000000000 --- a/guest-programs/bench-hash/src/compact_blake2b.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! A code-size-compact blake2b-256: a single round body in a loop, instead -//! of the fully-unrolled form used by `blake2b_simd` (and RustCrypto -//! `blake2`). -//! -//! Rationale: on PVM the recompiled unrolled compression carries ~2x the -//! instructions of native (spills + 3-op to 2-op translation), overflowing -//! the host CPU's uop cache and capping the front-end at legacy-decoder -//! width. Rolling the rounds into a loop keeps the hot code cache-resident -//! while the state still lives in registers; the costs are the loop control -//! and SIGMA-indexed (instead of constant-offset) message loads. -//! -//! The loop bound is laundered through `black_box` so the compiler cannot -//! unroll it back. - -pub(crate) const IV: [u64; 8] = [ - 0x6a09e667f3bcc908, - 0xbb67ae8584caa73b, - 0x3c6ef372fe94f82b, - 0xa54ff53a5f1d36f1, - 0x510e527fade682d1, - 0x9b05688c2b3e6c1f, - 0x1f83d9abfb41bd6b, - 0x5be0cd19137e2179, -]; - -const SIGMA: [[u8; 16]; 10] = [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], - [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], - [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], - [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], - [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], - [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], - [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], - [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], - [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], -]; - -#[inline(always)] -fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { - v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); - v[d] = (v[d] ^ v[a]).rotate_right(32); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(24); - v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); - v[d] = (v[d] ^ v[a]).rotate_right(16); - v[c] = v[c].wrapping_add(v[d]); - v[b] = (v[b] ^ v[c]).rotate_right(63); -} - -#[inline(never)] -fn compress(h: &mut [u64; 8], block: &[u8; 128], t: u128, last: bool) { - let mut m = [0u64; 16]; - for (i, chunk) in block.chunks_exact(8).enumerate() { - m[i] = u64::from_le_bytes(chunk.try_into().unwrap()); - } - - let mut v = [0u64; 16]; - v[..8].copy_from_slice(h); - v[8..].copy_from_slice(&IV); - v[12] ^= t as u64; - v[13] ^= (t >> 64) as u64; - if last { - v[14] = !v[14]; - } - - // One round body; the opaque bound keeps it a real loop. The sigma row - // is selected with a rotating index (no modulo — an opaque `r % 10` - // would compile to a real division per round), and the message indices - // are masked so the loads are provably in-bounds (no bounds checks). - let mut s_idx = 0; - for _ in 0..core::hint::black_box(12usize) { - let s = &SIGMA[s_idx]; - s_idx += 1; - if s_idx == 10 { - s_idx = 0; - } - g(&mut v, 0, 4, 8, 12, m[s[0] as usize & 15], m[s[1] as usize & 15]); - g(&mut v, 1, 5, 9, 13, m[s[2] as usize & 15], m[s[3] as usize & 15]); - g(&mut v, 2, 6, 10, 14, m[s[4] as usize & 15], m[s[5] as usize & 15]); - g(&mut v, 3, 7, 11, 15, m[s[6] as usize & 15], m[s[7] as usize & 15]); - g(&mut v, 0, 5, 10, 15, m[s[8] as usize & 15], m[s[9] as usize & 15]); - g(&mut v, 1, 6, 11, 12, m[s[10] as usize & 15], m[s[11] as usize & 15]); - g(&mut v, 2, 7, 8, 13, m[s[12] as usize & 15], m[s[13] as usize & 15]); - g(&mut v, 3, 4, 9, 14, m[s[14] as usize & 15], m[s[15] as usize & 15]); - } - - for i in 0..8 { - h[i] ^= v[i] ^ v[i + 8]; - } -} - -pub fn blake2b_256(input: &[u8]) -> [u8; 32] { - let mut h = IV; - h[0] ^= 0x0101_0000 ^ 32; // digest_length = 32, key = 0, fanout = 1, depth = 1 - - let mut t: u128 = 0; - let mut offset = 0; - while input.len() - offset > 128 { - let block: &[u8; 128] = input[offset..offset + 128].try_into().unwrap(); - t += 128; - compress(&mut h, block, t, false); - offset += 128; - } - - let rem = &input[offset..]; - let mut block = [0u8; 128]; - block[..rem.len()].copy_from_slice(rem); - t += rem.len() as u128; - compress(&mut h, &block, t, true); - - let mut out = [0u8; 32]; - for i in 0..4 { - out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); - } - out -} diff --git a/guest-programs/bench-hash/src/main.rs b/guest-programs/bench-hash/src/main.rs deleted file mode 100644 index b1ca3b408..000000000 --- a/guest-programs/bench-hash/src/main.rs +++ /dev/null @@ -1,146 +0,0 @@ -#![no_std] -#![no_main] - -include!("../../bench-common.rs"); - -const MAX_LEN: usize = 1024 * 1024; - -struct State { - memory: alloc::vec::Vec, -} - -define_benchmark! { - heap_size = MAX_LEN + 64 * 1024, - state = State { - memory: alloc::vec::Vec::new(), - }, -} - -fn benchmark_initialize(state: &mut State) { - state.memory.resize(MAX_LEN, 0); - for (index, byte) in state.memory.iter_mut().enumerate() { - *byte = index as u8; - } -} - -// Entry point for the generic harness; the real benchmarks are the -// parameterized `benchmark_*` exports below, driven by benchtool's -// `bench-hash` subcommand. -fn benchmark_run(state: &mut State) { - core::hint::black_box(chained(&mut state.memory, 32, 1, blake2_256_once)); -} - -/// The output buffer fits the largest digest (keccak_512); each hash function -/// writes its own digest length and returns it. -type Output = [u8; 64]; - -fn blake2b_once(input: &[u8], out: &mut Output, hash_length: usize) -> usize { - let hash = blake2b_simd::Params::new().hash_length(hash_length).hash(input); - out[..hash_length].copy_from_slice(hash.as_bytes()); - hash_length -} - -fn blake2_128_once(input: &[u8], out: &mut Output) -> usize { - blake2b_once(input, out, 16) -} - -fn blake2_256_once(input: &[u8], out: &mut Output) -> usize { - blake2b_once(input, out, 32) -} - -mod compact_blake2b; - -// Same function as `blake2_256`, but computed by the code-size-compact -// rounds-loop implementation; its checksums must match `blake2_256`'s. -fn blake2_256_compact_once(input: &[u8], out: &mut Output) -> usize { - out[..32].copy_from_slice(&compact_blake2b::blake2b_256(input)); - 32 -} - -mod asm_blake2b; - -// Same function as `blake2_256`, but with a hand-scheduled RISC-V assembly -// compression function; its checksums must match `blake2_256`'s. On non-PVM -// targets it falls back to the compact implementation. -fn blake2_256_asm_once(input: &[u8], out: &mut Output) -> usize { - out[..32].copy_from_slice(&asm_blake2b::blake2b_256(input)); - 32 -} - -fn keccak_256_once(input: &[u8], out: &mut Output) -> usize { - use sha3::Digest; - let mut hasher = sha3::Keccak256::new(); - hasher.update(input); - out[..32].copy_from_slice(&hasher.finalize()); - 32 -} - -fn keccak_512_once(input: &[u8], out: &mut Output) -> usize { - use sha3::Digest; - let mut hasher = sha3::Keccak512::new(); - hasher.update(input); - out[..64].copy_from_slice(&hasher.finalize()); - 64 -} - -fn sha2_256_once(input: &[u8], out: &mut Output) -> usize { - use sha2::Digest; - let mut hasher = sha2::Sha256::new(); - hasher.update(input); - out[..32].copy_from_slice(&hasher.finalize()); - 32 -} - -/// N seeded XxHash64 passes, like `sp_core::hashing::twox_*`. -fn twox_once(input: &[u8], out: &mut Output) -> usize { - use core::hash::Hasher; - for seed in 0..WORDS { - let mut hasher = twox_hash::XxHash64::with_seed(seed as u64); - hasher.write(input); - out[seed * 8..(seed + 1) * 8].copy_from_slice(&hasher.finish().to_le_bytes()); - } - WORDS * 8 -} - -/// Hashes `memory[..len]` `times` times, feeding each round's output back -/// into the start of the buffer so every iteration depends on the previous -/// one (no loop-invariant hoisting), and returns the first 8 bytes of the -/// final digest so the whole chain is observable (no dead-code elimination). -fn chained( - memory: &mut [u8], - len: u64, - times: u64, - hash_once: impl Fn(&[u8], &mut Output) -> usize, -) -> u64 { - let len = (len as usize).min(memory.len()).max(1); - let mut out = [0u8; 64]; - for _ in 0..times { - let out_len = hash_once(&memory[..len], &mut out); - let feedback = len.min(out_len); - memory[..feedback].copy_from_slice(&out[..feedback]); - } - u64::from_le_bytes(out[..8].try_into().unwrap()) -} - -macro_rules! export_hash_benchmark { - ($name:ident, $hash_once:expr) => { - #[cfg_attr(target_env = "polkavm", polkavm_derive::polkavm_export)] - #[no_mangle] - pub extern "C" fn $name(len: u64, times: u64) -> u64 { - let state = unsafe { &mut *core::ptr::addr_of_mut!(STATE) }; - chained(&mut state.memory, len, times, $hash_once) - } - }; -} - -// Named after the `sp_io::hashing` host functions they mirror. -export_hash_benchmark!(benchmark_blake2_128, blake2_128_once); -export_hash_benchmark!(benchmark_blake2_256, blake2_256_once); -export_hash_benchmark!(benchmark_blake2_256_compact, blake2_256_compact_once); -export_hash_benchmark!(benchmark_blake2_256_asm, blake2_256_asm_once); -export_hash_benchmark!(benchmark_keccak_256, keccak_256_once); -export_hash_benchmark!(benchmark_keccak_512, keccak_512_once); -export_hash_benchmark!(benchmark_sha2_256, sha2_256_once); -export_hash_benchmark!(benchmark_twox_64, twox_once::<1>); -export_hash_benchmark!(benchmark_twox_128, twox_once::<2>); -export_hash_benchmark!(benchmark_twox_256, twox_once::<4>); From 70a44bee8dd7294534363dc629efb07d2c44384a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:10:47 +0200 Subject: [PATCH 15/25] bench-hash: crates --- guest-programs/bench-blake2-128/Cargo.toml | 29 + guest-programs/bench-blake2-128/src/main.rs | 12 + .../bench-blake2-256-asm/Cargo.toml | 28 + .../bench-blake2-256-asm/src/asm_blake2b.rs | 156 ++ .../src/blake2b_compress.S | 2071 +++++++++++++++++ .../bench-blake2-256-asm/src/main.rs | 16 + guest-programs/bench-blake2-256/Cargo.toml | 29 + guest-programs/bench-blake2-256/src/main.rs | 12 + guest-programs/bench-keccak-256/Cargo.toml | 29 + guest-programs/bench-keccak-256/src/main.rs | 14 + guest-programs/bench-keccak-512/Cargo.toml | 29 + guest-programs/bench-keccak-512/src/main.rs | 14 + guest-programs/bench-sha2-256/Cargo.toml | 29 + guest-programs/bench-sha2-256/src/main.rs | 14 + guest-programs/bench-twox-128/Cargo.toml | 29 + guest-programs/bench-twox-128/src/main.rs | 16 + guest-programs/bench-twox-256/Cargo.toml | 29 + guest-programs/bench-twox-256/src/main.rs | 16 + guest-programs/bench-twox-64/Cargo.toml | 29 + guest-programs/bench-twox-64/src/main.rs | 16 + 20 files changed, 2617 insertions(+) create mode 100644 guest-programs/bench-blake2-128/Cargo.toml create mode 100644 guest-programs/bench-blake2-128/src/main.rs create mode 100644 guest-programs/bench-blake2-256-asm/Cargo.toml create mode 100644 guest-programs/bench-blake2-256-asm/src/asm_blake2b.rs create mode 100644 guest-programs/bench-blake2-256-asm/src/blake2b_compress.S create mode 100644 guest-programs/bench-blake2-256-asm/src/main.rs create mode 100644 guest-programs/bench-blake2-256/Cargo.toml create mode 100644 guest-programs/bench-blake2-256/src/main.rs create mode 100644 guest-programs/bench-keccak-256/Cargo.toml create mode 100644 guest-programs/bench-keccak-256/src/main.rs create mode 100644 guest-programs/bench-keccak-512/Cargo.toml create mode 100644 guest-programs/bench-keccak-512/src/main.rs create mode 100644 guest-programs/bench-sha2-256/Cargo.toml create mode 100644 guest-programs/bench-sha2-256/src/main.rs create mode 100644 guest-programs/bench-twox-128/Cargo.toml create mode 100644 guest-programs/bench-twox-128/src/main.rs create mode 100644 guest-programs/bench-twox-256/Cargo.toml create mode 100644 guest-programs/bench-twox-256/src/main.rs create mode 100644 guest-programs/bench-twox-64/Cargo.toml create mode 100644 guest-programs/bench-twox-64/src/main.rs diff --git a/guest-programs/bench-blake2-128/Cargo.toml b/guest-programs/bench-blake2-128/Cargo.toml new file mode 100644 index 000000000..99a81a563 --- /dev/null +++ b/guest-programs/bench-blake2-128/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-blake2-128" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_blake2_128" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-blake2-128" +path = "src/main.rs" + +[dependencies] +blake2b_simd = { version = "1", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-blake2-128/src/main.rs b/guest-programs/bench-blake2-128/src/main.rs new file mode 100644 index 000000000..e7e49c013 --- /dev/null +++ b/guest-programs/bench-blake2-128/src/main.rs @@ -0,0 +1,12 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// 16-byte blake2b, as `sp_io::hashing::blake2_128`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + let hash = blake2b_simd::Params::new().hash_length(16).hash(input); + out[..16].copy_from_slice(hash.as_bytes()); + 16 +} diff --git a/guest-programs/bench-blake2-256-asm/Cargo.toml b/guest-programs/bench-blake2-256-asm/Cargo.toml new file mode 100644 index 000000000..b2cd35441 --- /dev/null +++ b/guest-programs/bench-blake2-256-asm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "bench-blake2-256-asm" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_blake2_256_asm" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-blake2-256-asm" +path = "src/main.rs" + +[dependencies] +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-blake2-256-asm/src/asm_blake2b.rs b/guest-programs/bench-blake2-256-asm/src/asm_blake2b.rs new file mode 100644 index 000000000..f3ebf44e0 --- /dev/null +++ b/guest-programs/bench-blake2-256-asm/src/asm_blake2b.rs @@ -0,0 +1,156 @@ +// blake2b-256 with a hand-scheduled RISC-V assembly compression function +// (`blake2b_compress.S`, generated). +// +// Rationale: the recompiled output of the portable Rust implementation is +// issue-bound — LLVM's register allocation for the 13-register rv64e target +// spills far more than necessary and its 3-operand code forces the +// recompiler to emit register-copy `mov`s. The assembly version pins the +// state's a/b rows (v0..v7) in registers, streams the c/d rows through a +// 64-byte stack frame, uses strictly two-operand form (no recompiler movs), +// and bakes the message schedule into constant load offsets. +// +// On non-riscv64 targets (host builds, riscv32) this falls back to a plain +// Rust implementation (a rounds loop; merged from the former +// `compact_blake2b` module) so the export exists everywhere and outputs +// stay comparable. + +const IV: [u64; 8] = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179, +]; + +#[cfg(target_arch = "riscv64")] +core::arch::global_asm!(include_str!("blake2b_compress.S")); + +#[cfg(target_arch = "riscv64")] +extern "C" { + /// Processes `nblocks` consecutive 128-byte blocks; the byte counter for + /// block k is `t_base + 128*k` (wrapping). `last != 0` finalizes every + /// block in the batch, so callers pass `nblocks = 1` for the last block. + fn blake2b_compress_many(h: *mut u64, data: *const u8, nblocks: u64, t_base: u64, iv: *const u64, last: u64); +} + +#[cfg(target_arch = "riscv64")] +pub fn blake2b_256(input: &[u8]) -> [u8; 32] { + let mut h = IV; + h[0] ^= 0x0101_0000 ^ 32; + + // All full blocks except a full last block (the last block is always + // finalized separately below). + let full = input.len().saturating_sub(1) / 128; + if full > 0 { + unsafe { blake2b_compress_many(h.as_mut_ptr(), input.as_ptr(), full as u64, 0, IV.as_ptr(), 0) }; + } + + let rem = &input[full * 128..]; + let mut block = [0u8; 128]; + block[..rem.len()].copy_from_slice(rem); + let t_base = (input.len() as u64).wrapping_sub(128); + unsafe { blake2b_compress_many(h.as_mut_ptr(), block.as_ptr(), 1, t_base, IV.as_ptr(), 1) }; + + let mut out = [0u8; 32]; + for i in 0..4 { + out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); + } + out +} + +#[cfg(not(target_arch = "riscv64"))] +const SIGMA: [[u8; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +#[cfg(not(target_arch = "riscv64"))] +#[inline(always)] +fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) { + v[a] = v[a].wrapping_add(v[b]).wrapping_add(x); + v[d] = (v[d] ^ v[a]).rotate_right(32); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(24); + v[a] = v[a].wrapping_add(v[b]).wrapping_add(y); + v[d] = (v[d] ^ v[a]).rotate_right(16); + v[c] = v[c].wrapping_add(v[d]); + v[b] = (v[b] ^ v[c]).rotate_right(63); +} + +#[cfg(not(target_arch = "riscv64"))] +fn compress(h: &mut [u64; 8], block: &[u8; 128], t: u128, last: bool) { + let mut m = [0u64; 16]; + for (i, chunk) in block.chunks_exact(8).enumerate() { + m[i] = u64::from_le_bytes(chunk.try_into().unwrap()); + } + + let mut v = [0u64; 16]; + v[..8].copy_from_slice(h); + v[8..].copy_from_slice(&IV); + v[12] ^= t as u64; + v[13] ^= (t >> 64) as u64; + if last { + v[14] = !v[14]; + } + + let mut s_idx = 0; + for _ in 0..12 { + let s = &SIGMA[s_idx]; + s_idx += 1; + if s_idx == 10 { + s_idx = 0; + } + g(&mut v, 0, 4, 8, 12, m[s[0] as usize & 15], m[s[1] as usize & 15]); + g(&mut v, 1, 5, 9, 13, m[s[2] as usize & 15], m[s[3] as usize & 15]); + g(&mut v, 2, 6, 10, 14, m[s[4] as usize & 15], m[s[5] as usize & 15]); + g(&mut v, 3, 7, 11, 15, m[s[6] as usize & 15], m[s[7] as usize & 15]); + g(&mut v, 0, 5, 10, 15, m[s[8] as usize & 15], m[s[9] as usize & 15]); + g(&mut v, 1, 6, 11, 12, m[s[10] as usize & 15], m[s[11] as usize & 15]); + g(&mut v, 2, 7, 8, 13, m[s[12] as usize & 15], m[s[13] as usize & 15]); + g(&mut v, 3, 4, 9, 14, m[s[14] as usize & 15], m[s[15] as usize & 15]); + } + + for i in 0..8 { + h[i] ^= v[i] ^ v[i + 8]; + } +} + +/// Plain-Rust fallback (a rounds loop; merged from the former +/// `compact_blake2b` module) for targets without the assembly. +#[cfg(not(target_arch = "riscv64"))] +pub fn blake2b_256(input: &[u8]) -> [u8; 32] { + let mut h = IV; + h[0] ^= 0x0101_0000 ^ 32; // digest_length = 32, key = 0, fanout = 1, depth = 1 + + let mut t: u128 = 0; + let mut offset = 0; + while input.len() - offset > 128 { + let block: &[u8; 128] = input[offset..offset + 128].try_into().unwrap(); + t += 128; + compress(&mut h, block, t, false); + offset += 128; + } + + let rem = &input[offset..]; + let mut block = [0u8; 128]; + block[..rem.len()].copy_from_slice(rem); + t += rem.len() as u128; + compress(&mut h, &block, t, true); + + let mut out = [0u8; 32]; + for i in 0..4 { + out[i * 8..][..8].copy_from_slice(&h[i].to_le_bytes()); + } + out +} diff --git a/guest-programs/bench-blake2-256-asm/src/blake2b_compress.S b/guest-programs/bench-blake2-256-asm/src/blake2b_compress.S new file mode 100644 index 000000000..b9779d5db --- /dev/null +++ b/guest-programs/bench-blake2-256-asm/src/blake2b_compress.S @@ -0,0 +1,2071 @@ +// Hand-scheduled blake2b compression loop for PVM (rv64e + zbb). +// Generated - see the report / bench-hash sources for context. +// fn blake2b_compress_many(h: *mut u64 [a0], data: *const u8 [a1], +// nblocks: u64 [a2], t_base: u64 [a3], +// iv: *const u64 [a4], last: u64 [a5]) +// +// Processes `nblocks` consecutive 128-byte blocks; the byte counter for +// block k is t_base + 128*k (wrapping, so a short/empty last block passes +// t_base = total_len - 128). If `last` != 0 every block in the batch is +// finalized (callers pass nblocks = 1 for the last block). +// h is kept in the pinned registers across blocks; the a/b rows of the +// state (v0..v7) never touch memory inside a block. +.section .text.blake2b_compress_many,"ax" +.globl blake2b_compress_many +blake2b_compress_many: + addi sp, sp, -240 + sd ra, 216(sp) + sd s0, 224(sp) + sd s1, 232(sp) + sd a0, 208(sp) + sd a3, 192(sp) + sd a2, 200(sp) + // IV copy; IV[6] pre-masked with the finalization flag + ld t1, 0(a4) + sd t1, 64(sp) + ld t1, 8(a4) + sd t1, 72(sp) + ld t1, 16(a4) + sd t1, 80(sp) + ld t1, 24(a4) + sd t1, 88(sp) + ld t1, 32(a4) + sd t1, 96(sp) + ld t1, 40(a4) + sd t1, 104(sp) + ld t1, 48(a4) + sd t1, 112(sp) + ld t1, 56(a4) + sd t1, 120(sp) + ld t1, 112(sp) + neg t2, a5 + xor t1, t1, t2 + sd t1, 112(sp) + mv t0, a1 + // v0..v7 = h[0..7] (a0 loaded last - it is the source pointer) + ld s1, 56(a0) + ld s0, 48(a0) + ld a5, 40(a0) + ld a4, 32(a0) + ld a3, 24(a0) + ld a2, 16(a0) + ld a1, 8(a0) + ld a0, 0(a0) +.Lblock: + // save h_old; the pinned registers become the working state v0..v7 + sd a0, 128(sp) + sd a1, 136(sp) + sd a2, 144(sp) + sd a3, 152(sp) + sd a4, 160(sp) + sd a5, 168(sp) + sd s0, 176(sp) + sd s1, 184(sp) + // t += 128; v8..v15 = IV copy, v12 ^= t + ld t1, 192(sp) + addi t1, t1, 128 + sd t1, 192(sp) + ld t2, 96(sp) + xor t2, t2, t1 + sd t2, 32(sp) + ld t1, 64(sp) + sd t1, 0(sp) + ld t1, 72(sp) + sd t1, 8(sp) + ld t1, 80(sp) + sd t1, 16(sp) + ld t1, 88(sp) + sd t1, 24(sp) + ld t1, 104(sp) + sd t1, 40(sp) + ld t1, 112(sp) + sd t1, 48(sp) + ld t1, 120(sp) + sd t1, 56(sp) + // round 0 + ld t1, 0(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 8(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 16(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 24(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 32(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 80(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 96(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 1 + ld t1, 112(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 32(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 64(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 120(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 104(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 96(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 24(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 2 + ld t1, 88(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 64(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 96(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 0(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 40(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 120(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 80(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 112(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 24(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 48(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 56(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 8(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 72(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 32(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 3 + ld t1, 56(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 72(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 24(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 8(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 104(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 96(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 112(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 16(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 48(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 40(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 80(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 32(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 0(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 120(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 64(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 4 + ld t1, 72(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 0(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 40(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 56(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 16(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 32(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 80(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 120(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 8(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 88(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 96(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 64(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 24(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 104(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 5 + ld t1, 16(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 96(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 48(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 80(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 64(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 24(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 32(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 104(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 56(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 120(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 112(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 72(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 6 + ld t1, 96(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 40(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 8(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 120(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 112(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 104(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 32(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 80(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 0(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 56(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 48(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 24(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 72(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 16(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 88(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 7 + ld t1, 104(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 88(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 56(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 112(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 96(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 8(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 24(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 72(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 0(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 120(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 32(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 64(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 16(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 8 + ld t1, 48(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 112(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 88(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 24(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 0(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 64(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 96(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 16(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 104(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 56(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 8(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 32(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 80(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 40(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 9 + ld t1, 80(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 16(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 64(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 32(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 56(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 48(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 8(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 40(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 120(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 88(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 112(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 24(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 96(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 104(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 0(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 10 + ld t1, 0(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 8(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 16(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 24(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 32(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 40(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 48(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 64(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 72(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 80(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 88(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 96(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 104(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 112(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 120(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // round 11 + ld t1, 112(t0) + add a0, a0, a4 + add a0, a0, t1 + ld t2, 32(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 80(t0) + add a0, a0, a4 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor a4, a4, t1 + rori a4, a4, 63 + ld t1, 32(t0) + add a1, a1, a5 + add a1, a1, t1 + ld t2, 40(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 64(t0) + add a1, a1, a5 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 72(t0) + add a2, a2, s0 + add a2, a2, t1 + ld t2, 48(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 120(t0) + add a2, a2, s0 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 104(t0) + add a3, a3, s1 + add a3, a3, t1 + ld t2, 56(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 48(t0) + add a3, a3, s1 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 8(t0) + add a0, a0, a5 + add a0, a0, t1 + ld t2, 56(sp) + xor t2, t2, a0 + rori t2, t2, 32 + ld t1, 16(sp) + add t1, t1, t2 + xor a5, a5, t1 + rori a5, a5, 24 + ld ra, 96(t0) + add a0, a0, a5 + add a0, a0, ra + xor t2, t2, a0 + rori t2, t2, 16 + sd t2, 56(sp) + add t1, t1, t2 + sd t1, 16(sp) + xor a5, a5, t1 + rori a5, a5, 63 + ld t1, 0(t0) + add a1, a1, s0 + add a1, a1, t1 + ld t2, 32(sp) + xor t2, t2, a1 + rori t2, t2, 32 + ld t1, 24(sp) + add t1, t1, t2 + xor s0, s0, t1 + rori s0, s0, 24 + ld ra, 16(t0) + add a1, a1, s0 + add a1, a1, ra + xor t2, t2, a1 + rori t2, t2, 16 + sd t2, 32(sp) + add t1, t1, t2 + sd t1, 24(sp) + xor s0, s0, t1 + rori s0, s0, 63 + ld t1, 88(t0) + add a2, a2, s1 + add a2, a2, t1 + ld t2, 40(sp) + xor t2, t2, a2 + rori t2, t2, 32 + ld t1, 0(sp) + add t1, t1, t2 + xor s1, s1, t1 + rori s1, s1, 24 + ld ra, 56(t0) + add a2, a2, s1 + add a2, a2, ra + xor t2, t2, a2 + rori t2, t2, 16 + sd t2, 40(sp) + add t1, t1, t2 + sd t1, 0(sp) + xor s1, s1, t1 + rori s1, s1, 63 + ld t1, 40(t0) + add a3, a3, a4 + add a3, a3, t1 + ld t2, 48(sp) + xor t2, t2, a3 + rori t2, t2, 32 + ld t1, 8(sp) + add t1, t1, t2 + xor a4, a4, t1 + rori a4, a4, 24 + ld ra, 24(t0) + add a3, a3, a4 + add a3, a3, ra + xor t2, t2, a3 + rori t2, t2, 16 + sd t2, 48(sp) + add t1, t1, t2 + sd t1, 8(sp) + xor a4, a4, t1 + rori a4, a4, 63 + // h ^= v[0..7] ^ v[8..15]; the result stays pinned for the next block + ld t1, 128(sp) + ld t2, 0(sp) + xor a0, a0, t1 + xor a0, a0, t2 + ld t1, 136(sp) + ld t2, 8(sp) + xor a1, a1, t1 + xor a1, a1, t2 + ld t1, 144(sp) + ld t2, 16(sp) + xor a2, a2, t1 + xor a2, a2, t2 + ld t1, 152(sp) + ld t2, 24(sp) + xor a3, a3, t1 + xor a3, a3, t2 + ld t1, 160(sp) + ld t2, 32(sp) + xor a4, a4, t1 + xor a4, a4, t2 + ld t1, 168(sp) + ld t2, 40(sp) + xor a5, a5, t1 + xor a5, a5, t2 + ld t1, 176(sp) + ld t2, 48(sp) + xor s0, s0, t1 + xor s0, s0, t2 + ld t1, 184(sp) + ld t2, 56(sp) + xor s1, s1, t1 + xor s1, s1, t2 + // next block + addi t0, t0, 128 + ld t1, 200(sp) + addi t1, t1, -1 + sd t1, 200(sp) + beqz t1, .Ldone + j .Lblock +.Ldone: + ld t1, 208(sp) + sd a0, 0(t1) + sd a1, 8(t1) + sd a2, 16(t1) + sd a3, 24(t1) + sd a4, 32(t1) + sd a5, 40(t1) + sd s0, 48(t1) + sd s1, 56(t1) + ld ra, 216(sp) + ld s0, 224(sp) + ld s1, 232(sp) + addi sp, sp, 240 + ret diff --git a/guest-programs/bench-blake2-256-asm/src/main.rs b/guest-programs/bench-blake2-256-asm/src/main.rs new file mode 100644 index 000000000..68a736aea --- /dev/null +++ b/guest-programs/bench-blake2-256-asm/src/main.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +mod asm_blake2b; + +// blake2b-256 with a hand-scheduled RISC-V assembly compression function on +// riscv64; other targets fall back to a plain Rust implementation (see +// `asm_blake2b.rs`). Compare against `bench-blake2-256` (blake2b_simd) to see +// what hand-tuned codegen buys under the recompiler. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + out[..32].copy_from_slice(&asm_blake2b::blake2b_256(input)); + 32 +} diff --git a/guest-programs/bench-blake2-256/Cargo.toml b/guest-programs/bench-blake2-256/Cargo.toml new file mode 100644 index 000000000..7c617ed06 --- /dev/null +++ b/guest-programs/bench-blake2-256/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-blake2-256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_blake2_256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-blake2-256" +path = "src/main.rs" + +[dependencies] +blake2b_simd = { version = "1", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-blake2-256/src/main.rs b/guest-programs/bench-blake2-256/src/main.rs new file mode 100644 index 000000000..5e97396b6 --- /dev/null +++ b/guest-programs/bench-blake2-256/src/main.rs @@ -0,0 +1,12 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// 32-byte blake2b, as `sp_io::hashing::blake2_256`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + let hash = blake2b_simd::Params::new().hash_length(32).hash(input); + out[..32].copy_from_slice(hash.as_bytes()); + 32 +} diff --git a/guest-programs/bench-keccak-256/Cargo.toml b/guest-programs/bench-keccak-256/Cargo.toml new file mode 100644 index 000000000..4f597a1ac --- /dev/null +++ b/guest-programs/bench-keccak-256/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-keccak-256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_keccak_256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-keccak-256" +path = "src/main.rs" + +[dependencies] +sha3 = { version = "0.10", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-keccak-256/src/main.rs b/guest-programs/bench-keccak-256/src/main.rs new file mode 100644 index 000000000..79266e04b --- /dev/null +++ b/guest-programs/bench-keccak-256/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// As `sp_io::hashing::keccak_256`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use sha3::Digest; + let mut hasher = sha3::Keccak256::new(); + hasher.update(input); + out[..32].copy_from_slice(&hasher.finalize()); + 32 +} diff --git a/guest-programs/bench-keccak-512/Cargo.toml b/guest-programs/bench-keccak-512/Cargo.toml new file mode 100644 index 000000000..66b7d85c8 --- /dev/null +++ b/guest-programs/bench-keccak-512/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-keccak-512" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_keccak_512" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-keccak-512" +path = "src/main.rs" + +[dependencies] +sha3 = { version = "0.10", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-keccak-512/src/main.rs b/guest-programs/bench-keccak-512/src/main.rs new file mode 100644 index 000000000..1e45a7fbc --- /dev/null +++ b/guest-programs/bench-keccak-512/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// As `sp_io::hashing::keccak_512`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use sha3::Digest; + let mut hasher = sha3::Keccak512::new(); + hasher.update(input); + out[..64].copy_from_slice(&hasher.finalize()); + 64 +} diff --git a/guest-programs/bench-sha2-256/Cargo.toml b/guest-programs/bench-sha2-256/Cargo.toml new file mode 100644 index 000000000..57e3bdd08 --- /dev/null +++ b/guest-programs/bench-sha2-256/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-sha2-256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_sha2_256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-sha2-256" +path = "src/main.rs" + +[dependencies] +sha2 = { version = "0.10", default-features = false } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-sha2-256/src/main.rs b/guest-programs/bench-sha2-256/src/main.rs new file mode 100644 index 000000000..e4ce28cfe --- /dev/null +++ b/guest-programs/bench-sha2-256/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// As `sp_io::hashing::sha2_256`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(input); + out[..32].copy_from_slice(&hasher.finalize()); + 32 +} diff --git a/guest-programs/bench-twox-128/Cargo.toml b/guest-programs/bench-twox-128/Cargo.toml new file mode 100644 index 000000000..6ae06e115 --- /dev/null +++ b/guest-programs/bench-twox-128/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-twox-128" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_twox_128" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-twox-128" +path = "src/main.rs" + +[dependencies] +twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-twox-128/src/main.rs b/guest-programs/bench-twox-128/src/main.rs new file mode 100644 index 000000000..0bcca116d --- /dev/null +++ b/guest-programs/bench-twox-128/src/main.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// 2 seeded XxHash64 pass(es), like `sp_core::hashing::twox_128`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use core::hash::Hasher; + for seed in 0..2u64 { + let mut hasher = twox_hash::XxHash64::with_seed(seed); + hasher.write(input); + out[seed as usize * 8..(seed as usize + 1) * 8].copy_from_slice(&hasher.finish().to_le_bytes()); + } + 2 * 8 +} diff --git a/guest-programs/bench-twox-256/Cargo.toml b/guest-programs/bench-twox-256/Cargo.toml new file mode 100644 index 000000000..7aa9cd230 --- /dev/null +++ b/guest-programs/bench-twox-256/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-twox-256" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_twox_256" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-twox-256" +path = "src/main.rs" + +[dependencies] +twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-twox-256/src/main.rs b/guest-programs/bench-twox-256/src/main.rs new file mode 100644 index 000000000..e603e99d8 --- /dev/null +++ b/guest-programs/bench-twox-256/src/main.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// 4 seeded XxHash64 pass(es), like `sp_core::hashing::twox_256`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use core::hash::Hasher; + for seed in 0..4u64 { + let mut hasher = twox_hash::XxHash64::with_seed(seed); + hasher.write(input); + out[seed as usize * 8..(seed as usize + 1) * 8].copy_from_slice(&hasher.finish().to_le_bytes()); + } + 4 * 8 +} diff --git a/guest-programs/bench-twox-64/Cargo.toml b/guest-programs/bench-twox-64/Cargo.toml new file mode 100644 index 000000000..8ec0098e4 --- /dev/null +++ b/guest-programs/bench-twox-64/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "bench-twox-64" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "bench_twox_64" +path = "src/main.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "bench-twox-64" +path = "src/main.rs" + +[dependencies] +twox-hash = { version = "2", default-features = false, features = ["xxhash64"] } +picoalloc = { workspace = true } + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[features] +# Link compiler_builtins' word-wise memcpy/memset (what real-world guests get). +default = ["builtins-mem"] +builtins-mem = [] + +[lints] +workspace = true diff --git a/guest-programs/bench-twox-64/src/main.rs b/guest-programs/bench-twox-64/src/main.rs new file mode 100644 index 000000000..4ac41c0b4 --- /dev/null +++ b/guest-programs/bench-twox-64/src/main.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +include!("../../bench-common.rs"); +include!("../../hash-bench-common.rs"); + +// 1 seeded XxHash64 pass(es), like `sp_core::hashing::twox_64`. +fn hash_once(input: &[u8], out: &mut [u8; 64]) -> usize { + use core::hash::Hasher; + for seed in 0..1u64 { + let mut hasher = twox_hash::XxHash64::with_seed(seed); + hasher.write(input); + out[seed as usize * 8..(seed as usize + 1) * 8].copy_from_slice(&hasher.finish().to_le_bytes()); + } + 1 * 8 +} From 03538a959f8016cbf7f46702fb36a5853f0fd217 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:32:41 +0200 Subject: [PATCH 16/25] blake2 asm tests added --- guest-programs/test-blake2-asm/Cargo.toml | 20 ++++ guest-programs/test-blake2-asm/src/main.rs | 32 +++++++ tools/hash-asm-tests/Cargo.toml | 21 +++++ tools/hash-asm-tests/tests/pvm_asm.rs | 103 +++++++++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 guest-programs/test-blake2-asm/Cargo.toml create mode 100644 guest-programs/test-blake2-asm/src/main.rs create mode 100644 tools/hash-asm-tests/Cargo.toml create mode 100644 tools/hash-asm-tests/tests/pvm_asm.rs diff --git a/guest-programs/test-blake2-asm/Cargo.toml b/guest-programs/test-blake2-asm/Cargo.toml new file mode 100644 index 000000000..83c46cf36 --- /dev/null +++ b/guest-programs/test-blake2-asm/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "test-blake2-asm" +version = "0.1.0" +edition = "2021" +publish = false + +# Guest program for hash-asm-tests: exposes bench-blake2-256-asm's blake2b +# (the RISC-V assembly implementation) so the test can execute it inside a +# PVM instance and compare digests against a host-side reference. Built by +# the test itself; not part of the benchmark suite. + +[[bin]] +name = "test-blake2-asm" +path = "src/main.rs" + +[target.'cfg(target_env = "polkavm")'.dependencies] +polkavm-derive = { path = "../../crates/polkavm-derive" } + +[lints] +workspace = true diff --git a/guest-programs/test-blake2-asm/src/main.rs b/guest-programs/test-blake2-asm/src/main.rs new file mode 100644 index 000000000..924481c8c --- /dev/null +++ b/guest-programs/test-blake2-asm/src/main.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { core::arch::asm!("unimp", options(noreturn)) } +} + +#[path = "../../bench-blake2-256-asm/src/asm_blake2b.rs"] +mod asm_blake2b; + +const MAX_LEN: usize = 1024 * 1024 + 16; + +static mut BUFFER: [u8; MAX_LEN] = [0; MAX_LEN]; +static mut DIGEST: [u8; 32] = [0; 32]; + +#[polkavm_derive::polkavm_export] +extern "C" fn buffer_ptr() -> u64 { + core::ptr::addr_of!(BUFFER) as u64 +} + +#[polkavm_derive::polkavm_export] +extern "C" fn digest_ptr() -> u64 { + core::ptr::addr_of!(DIGEST) as u64 +} + +#[polkavm_derive::polkavm_export] +extern "C" fn hash(len: u64) { + let input = unsafe { &(*core::ptr::addr_of!(BUFFER))[..len as usize] }; + let digest = asm_blake2b::blake2b_256(input); + unsafe { (*core::ptr::addr_of_mut!(DIGEST)).copy_from_slice(&digest) }; +} diff --git a/tools/hash-asm-tests/Cargo.toml b/tools/hash-asm-tests/Cargo.toml new file mode 100644 index 000000000..38db46815 --- /dev/null +++ b/tools/hash-asm-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "hash-asm-tests" +version = "0.1.0" +edition = "2021" +publish = false + +# Correctness test for bench-blake2-256-asm: executes the RISC-V assembly +# blake2b inside a PVM instance and compares digests against blake2b_simd. +# Lives outside guest-programs/ on purpose - its .cargo config (build-std, +# RISC-V default target) is incompatible with `cargo test`. + +[[test]] +name = "pvm_asm" +path = "tests/pvm_asm.rs" + +[dev-dependencies] +blake2b_simd = "1" +polkavm = { path = "../../crates/polkavm" } +polkavm-linker = { path = "../../crates/polkavm-linker" } + +[workspace] diff --git a/tools/hash-asm-tests/tests/pvm_asm.rs b/tools/hash-asm-tests/tests/pvm_asm.rs new file mode 100644 index 000000000..30d0ee96f --- /dev/null +++ b/tools/hash-asm-tests/tests/pvm_asm.rs @@ -0,0 +1,103 @@ +//! Executes bench-blake2-256-asm's RISC-V assembly blake2b inside a real PVM +//! instance (interpreter backend) and compares every digest against +//! `blake2b_simd` computed on the host. Sizes cover every block-boundary +//! neighborhood, where blake2b implementations typically break. +//! +//! The guest (`guest-programs/test-blake2-asm`) is built by this test with +//! the same toolchain/target the benchmarks use, mirroring how +//! crates/polkavm's own tests build their test blobs. + +use std::path::PathBuf; +use std::process::Command; + +fn build_guest_blob() -> Vec { + let guest_programs = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../guest-programs"); + + let mut args = polkavm_linker::TargetJsonArgs::default(); + args.is_64_bit = true; + args.rustc_version = polkavm_linker::RustcVersion::Legacy; + let target_json = polkavm_linker::target_json_path(args).unwrap(); + + let mut cmd = Command::new("cargo"); + // Scrub the outer invocation's toolchain pins so the guest builds with + // the toolchain pinned by guest-programs/rust-toolchain.toml (a nightly + // with rust-src, required by -Zbuild-std). + for (key, _) in std::env::vars() { + if key.contains("CARGO") || key.contains("RUSTC") || key == "RUSTUP_TOOLCHAIN" { + cmd.env_remove(&key); + } + } + let output = cmd + .args(["build", "-q", "--release", "-p", "test-blake2-asm", "--bin", "test-blake2-asm"]) + .arg("--target") + .arg(&target_json) + .arg("-Zbuild-std=core,alloc") + .current_dir(&guest_programs) + .output() + .unwrap(); + assert!( + output.status.success(), + "guest build failed:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + + let elf = std::fs::read(guest_programs.join("target/riscv64emac-unknown-none-polkavm/release/test-blake2-asm")).unwrap(); + polkavm_linker::program_from_elf( + polkavm_linker::Config::default(), + polkavm_linker::TargetInstructionSet::Latest, + &elf, + ) + .unwrap() +} + +/// Deterministic pseudo-random input, different for every size. +fn input_for(size: usize) -> Vec { + let mut state = 0x9e3779b97f4a7c15u64 ^ (size as u64); + (0..size) + .map(|_| { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + state as u8 + }) + .collect() +} + +#[test] +fn asm_blake2b_in_pvm_matches_blake2b_simd() { + let blob = build_guest_blob(); + + let mut config = polkavm::Config::from_env().unwrap(); + // Deterministic and available everywhere (no sandbox requirements). + config.set_backend(Some(polkavm::BackendKind::Interpreter)); + let engine = polkavm::Engine::new(&config).unwrap(); + let blob = polkavm::ProgramBlob::parse(blob.into()).unwrap(); + let module = polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob).unwrap(); + let linker = polkavm::Linker::<()>::new(); + let mut instance = linker.instantiate_pre(&module).unwrap().instantiate().unwrap(); + + let buffer_ptr: u64 = instance.call_typed_and_get_result(&mut (), "buffer_ptr", ()).unwrap(); + let digest_ptr: u64 = instance.call_typed_and_get_result(&mut (), "digest_ptr", ()).unwrap(); + + // The full range up to 512, plus every power-of-two neighborhood + // (2^n - 16 ..= 2^n + 16) up to the buffer size - the boundaries where + // hash implementations typically break. + let mut sizes = std::collections::BTreeSet::new(); + sizes.extend(1..=512usize); + for n in 10..=20u32 { + let power = 1usize << n; + sizes.extend(power - 16..=power + 16); + } + + for size in sizes { + let input = input_for(size); + if size > 0 { + instance.write_memory(buffer_ptr as u32, &input).unwrap(); + } + instance.call_typed(&mut (), "hash", (size as u64,)).unwrap(); + let digest = instance.read_memory(digest_ptr as u32, 32).unwrap(); + + let expected = blake2b_simd::Params::new().hash_length(32).hash(&input); + assert_eq!(digest, expected.as_bytes(), "mismatch at size {size}"); + } +} From ad6fde5e402fa776aaf3e1bdb74f29bffd1f5922 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 09:29:05 +0200 Subject: [PATCH 17/25] hash-bench-common added --- guest-programs/hash-bench-common.rs | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 guest-programs/hash-bench-common.rs diff --git a/guest-programs/hash-bench-common.rs b/guest-programs/hash-bench-common.rs new file mode 100644 index 000000000..cb1500ecb --- /dev/null +++ b/guest-programs/hash-bench-common.rs @@ -0,0 +1,57 @@ +// Shared implementation for the bench- guest benchmarks. +// +// Each crate defines `hash_once(input, out) -> digest_len` and includes this +// file. `run()` hashes `buffer[..size]` once, feeding up to 32 bytes of the +// digest back into the buffer so consecutive runs never hash identical +// input. The size defaults to `DEFAULT_SIZE`; the harness can override it +// through the optional `benchmark_set_size` export (`--size`). + +const MAX_LEN: usize = 1024 * 1024; +const DEFAULT_SIZE: usize = 4096; + +// One run() hashes at least this many bytes (in max(1, ITER_BYTES / size) +// chained iterations), so the harness's per-call overhead (e.g. the VM +// call entry) is amortized even for tiny input sizes. Result processing +// divides by the same formula to recover the per-hash time; per-row +// PVM-vs-native ratios are unaffected (the factor is identical on both +// sides). +const ITER_BYTES: usize = 64 * 1024; + +struct State { + memory: alloc::vec::Vec, + size: usize, +} + +define_benchmark! { + heap_size = MAX_LEN + 64 * 1024, + state = State { + memory: alloc::vec::Vec::new(), + size: DEFAULT_SIZE, + }, +} + +fn benchmark_initialize(state: &mut State) { + state.memory.resize(MAX_LEN, 0); + for (index, byte) in state.memory.iter_mut().enumerate() { + *byte = index as u8; + } +} + +fn benchmark_run(state: &mut State) { + let iterations = (ITER_BYTES / state.size).max(1); + let mut out = [0u8; 64]; + for _ in 0..iterations { + let out_len = hash_once(&state.memory[..state.size], &mut out); + let feedback = state.size.min(out_len).min(32); + state.memory[..feedback].copy_from_slice(&out[..feedback]); + } + core::hint::black_box(&out); +} + +#[cfg_attr(target_env = "polkavm", polkavm_derive::polkavm_export)] +#[no_mangle] +pub extern "C" fn benchmark_set_size(size: u64) { + assert!(size > 0 && size as usize <= MAX_LEN); + let state = unsafe { &mut *core::ptr::addr_of_mut!(STATE) }; + state.size = size as usize; +} From 6557a135ba76bbf21456de92f2a89f652db66c43 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:57:21 +0200 Subject: [PATCH 18/25] build-benchmarks.sh - new benchmarks + fixed toolchain --- guest-programs/build-benchmarks.sh | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/guest-programs/build-benchmarks.sh b/guest-programs/build-benchmarks.sh index 702cc4e85..07a655a5a 100755 --- a/guest-programs/build-benchmarks.sh +++ b/guest-programs/build-benchmarks.sh @@ -105,14 +105,24 @@ function build_benchmark() { RUSTFLAGS="-C target-cpu=mvp -C target-feature=-sign-ext $extra_flags" cargo build -q --target=wasm32-unknown-unknown --release --bin $1 -p $1 fi + # Native libraries are deliberately built with the stable toolchain (the + # directory's nightly pin exists only for -Zbuild-std guest builds; the + # pinned nightly's x86-64 codegen is measurably worse, e.g. ~3x slower + # SHA-NI sha256). Exception: bench-memset's compiler_builtins dependency + # requires nightly. + native_toolchain="+1.86.0" + if [ "$1" == "bench-memset" ]; then + native_toolchain="" + fi + if [ "${BUILD_NATIVE_X86_64}" == "1" ]; then echo "> Building: '$1' (native, x86_64)" - RUSTFLAGS="$extra_flags" cargo build -q --target=x86_64-unknown-linux-gnu --release --lib -p $1 + RUSTFLAGS="$extra_flags" cargo $native_toolchain build -q --target=x86_64-unknown-linux-gnu --release --lib -p $1 fi if [ "${BUILD_NATIVE_X86}" == "1" ]; then echo "> Building: '$1' (native, i686)" - RUSTFLAGS="$extra_flags" cargo build -q --target=i686-unknown-linux-gnu --release --lib -p $1 + RUSTFLAGS="$extra_flags" cargo $native_toolchain build -q --target=i686-unknown-linux-gnu --release --lib -p $1 fi if [ "${BUILD_CKBVM}" == "1" ]; then @@ -143,7 +153,15 @@ build_benchmark "bench-ecdsa-k256" build_benchmark "bench-ecdsa-libsecp" build_benchmark "bench-recover-k256" build_benchmark "bench-recover-libsecp" -build_benchmark "bench-hash" +build_benchmark "bench-blake2-128" +build_benchmark "bench-blake2-256" +build_benchmark "bench-blake2-256-asm" +build_benchmark "bench-keccak-256" +build_benchmark "bench-keccak-512" +build_benchmark "bench-sha2-256" +build_benchmark "bench-twox-64" +build_benchmark "bench-twox-128" +build_benchmark "bench-twox-256" if [ "${SOLANA_PLATFORM_TOOLS_DIR:-}" != "" ]; then unset SOLANA_PLATFORM_TOOLS_DIR From cd230d3533a88f330f726eb1c1cabf00a31340b9 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:58:48 +0200 Subject: [PATCH 19/25] build-hash-native: target-cpu=native versions of host hash algos --- guest-programs/build-hash-native.sh | 41 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/guest-programs/build-hash-native.sh b/guest-programs/build-hash-native.sh index 34549af0e..f69024f11 100755 --- a/guest-programs/build-hash-native.sh +++ b/guest-programs/build-hash-native.sh @@ -1,27 +1,36 @@ #!/bin/sh -# Builds the bench-hash host library in both variants: -# libbench_hash.so - host portable: runs on any x86-64; crates may -# still runtime-dispatch to fast paths (e.g. sha2 -# uses SHA-NI via cpufeatures when available) -# libbench_hash_native.so - host native: -C target-cpu=native, all ISA -# extensions of the BUILD machine enabled -# unconditionally +# Builds each hash benchmark's host library in two variants: +# libbench_.so - host portable: runs on any x86-64; crates may +# still runtime-dispatch to fast paths (e.g. sha2 +# uses SHA-NI via cpufeatures when available) +# libbench__native.so - host native: -C target-cpu=native, all ISA +# extensions of the BUILD machine enabled +# unconditionally # -# Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool's -# `bench-hash` subcommand auto-discovers them as "hash" and "hash-native". +# Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool +# auto-discovers them as "" and "-native". # -# WARNING: never copy libbench_hash_native.so between machines - it is -# compiled for the exact CPU it was built on and will crash (SIGILL) on CPUs -# lacking any of its instruction-set extensions. Always rebuild locally. +# WARNING: never copy *_native.so between machines - they are compiled for the +# exact CPU they were built on and will crash (SIGILL) on CPUs lacking any of +# their instruction-set extensions. Always rebuild locally. +# +# Built with the stable toolchain - see the note in build-benchmarks.sh. set -ex cd "${0%/*}" out=target/x86_64-unknown-linux-gnu/release -RUSTFLAGS="-C target-cpu=native" cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash -cp "$out/libbench_hash.so" "$out/libbench_hash_native.so" +for crate in bench-blake2-128 bench-blake2-256 bench-blake2-256-asm bench-keccak-256 bench-keccak-512 \ + bench-sha2-256 bench-twox-64 bench-twox-128 bench-twox-256; do + lib=$(echo "$crate" | tr - _) + RUSTFLAGS="-C target-cpu=native" cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + cp "$out/lib$lib.so" "$out/lib${lib}_native.so" +done -# Rebuild without the CPU features; overwrites libbench_hash.so with the portable variant. -cargo build --target=x86_64-unknown-linux-gnu --release --lib -p bench-hash +# Rebuild without the CPU features; overwrites the portable variants. +for crate in bench-blake2-128 bench-blake2-256 bench-blake2-256-asm bench-keccak-256 bench-keccak-512 \ + bench-sha2-256 bench-twox-64 bench-twox-128 bench-twox-256; do + cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" +done From a725c0be4bc6430457cd93590c2b339ede55ee42 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:59:18 +0200 Subject: [PATCH 20/25] Cargo.toml + lock --- guest-programs/Cargo.lock | 109 ++++++++++++++++++++++++++++++++++---- guest-programs/Cargo.toml | 11 +++- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/guest-programs/Cargo.lock b/guest-programs/Cargo.lock index 587337195..2d34344cb 100644 --- a/guest-programs/Cargo.lock +++ b/guest-programs/Cargo.lock @@ -32,6 +32,32 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bench-blake2-128" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "picoalloc", + "polkavm-derive", +] + +[[package]] +name = "bench-blake2-256" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "picoalloc", + "polkavm-derive", +] + +[[package]] +name = "bench-blake2-256-asm" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", +] + [[package]] name = "bench-ecdsa-k256" version = "0.1.0" @@ -83,6 +109,24 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "bench-keccak-256" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "sha3", +] + +[[package]] +name = "bench-keccak-512" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "sha3", +] + [[package]] name = "bench-memset" version = "0.1.0" @@ -140,6 +184,15 @@ dependencies = [ "polkavm-derive", ] +[[package]] +name = "bench-sha2-256" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "sha2", +] + [[package]] name = "bench-sr25519" version = "0.1.0" @@ -149,6 +202,33 @@ dependencies = [ "schnorrkel", ] +[[package]] +name = "bench-twox-128" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "twox-hash", +] + +[[package]] +name = "bench-twox-256" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "twox-hash", +] + +[[package]] +name = "bench-twox-64" +version = "0.1.0" +dependencies = [ + "picoalloc", + "polkavm-derive", + "twox-hash", +] + [[package]] name = "bitflags" version = "2.4.1" @@ -266,7 +346,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.119", ] [[package]] @@ -595,7 +675,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.119", ] [[package]] @@ -603,23 +683,23 @@ name = "polkavm-derive-impl-macro" version = "0.37.0" dependencies = [ "polkavm-derive-impl", - "syn 2.0.38", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -712,7 +792,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.119", ] [[package]] @@ -785,15 +865,22 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "test-blake2-asm" +version = "0.1.0" +dependencies = [ + "polkavm-derive", +] + [[package]] name = "test-blob" version = "0.1.0" @@ -858,5 +945,5 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.119", ] diff --git a/guest-programs/Cargo.toml b/guest-programs/Cargo.toml index 24242918c..452d3445b 100644 --- a/guest-programs/Cargo.toml +++ b/guest-programs/Cargo.toml @@ -25,7 +25,15 @@ members = [ "bench-ecdsa-libsecp", "bench-recover-k256", "bench-recover-libsecp", - "bench-hash", + "bench-blake2-128", + "bench-blake2-256", + "bench-blake2-256-asm", + "bench-keccak-256", + "bench-keccak-512", + "bench-sha2-256", + "bench-twox-64", + "bench-twox-128", + "bench-twox-256", "bench-pinky", "bench-prime-sieve", @@ -34,6 +42,7 @@ members = [ # Tests "test-blob", + "test-blake2-asm", ] [workspace.dependencies] From c16a7c8bdf4525bb78c13317eafc3a2dd12666b1 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:00:14 +0200 Subject: [PATCH 21/25] process-results utils --- tools/benchtool/process-crypto-results.sh | 53 +++++++++ tools/benchtool/process-hash-results.sh | 103 ++++++++++++++++++ tools/benchtool/process-results.sh | 126 ---------------------- 3 files changed, 156 insertions(+), 126 deletions(-) create mode 100755 tools/benchtool/process-crypto-results.sh create mode 100755 tools/benchtool/process-hash-results.sh delete mode 100755 tools/benchtool/process-results.sh diff --git a/tools/benchtool/process-crypto-results.sh b/tools/benchtool/process-crypto-results.sh new file mode 100755 index 000000000..3310ee81f --- /dev/null +++ b/tools/benchtool/process-crypto-results.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +# Turns the generic-harness output of run-crypto-benches into a markdown +# summary table: per benchmark, host vs PVM (64-bit, compiler, sync gas). +# +# The "host" column is whichever build the benchmark libraries were compiled +# with (host portable by default; host native if built with +# -C target-cpu=native) - label the resulting table accordingly. +# +# Usage: +# ./run-crypto-benches > crypto.txt +# ./process-crypto-results.sh crypto.txt > crypto-table.md + +set -eu + +awk ' +# lines look like: runtime//: ...runtime//: 355.61us +$1 ~ /^runtime\// { + split($1, path, "/") + bench = path[2] + backend = path[3]; sub(/:$/, "", backend) + time = $NF + + # normalize to nanoseconds + ns = time + if (time ~ /ns$/) { sub(/ns$/, "", ns); ns += 0 } + else if (time ~ /us$/) { sub(/us$/, "", ns); ns *= 1000 } + else if (time ~ /ms$/) { sub(/ms$/, "", ns); ns *= 1000000 } + else if (time ~ /s$/) { sub(/s$/, "", ns); ns *= 1000000000 } + else next + + if (!(bench in seen)) { seen[bench] = ++count; benches[count] = bench } + if (backend == "native") native[bench] = ns + if (backend == "polkavm64_compiler_sync_gas") pvm[bench] = ns +} + +function fmt(ns) { + if (ns == "") return "-" + if (ns < 1000) return sprintf("%.0f ns", ns) + if (ns < 1000000) return sprintf("%.2f µs", ns / 1000) + return sprintf("%.2f ms", ns / 1000000) +} + +END { + print "| benchmark | host | PVM (64-bit, sync gas) | ratio |" + print "|---|---:|---:|---:|" + for (i = 1; i <= count; i++) { + b = benches[i] + ratio = (native[b] != "" && pvm[b] != "") ? sprintf("%.2fx", pvm[b] / native[b]) : "-" + printf "| %s | %s | %s | %s |\n", b, fmt(native[b]), fmt(pvm[b]), ratio + } +} +' "$@" diff --git a/tools/benchtool/process-hash-results.sh b/tools/benchtool/process-hash-results.sh new file mode 100755 index 000000000..64f824c3a --- /dev/null +++ b/tools/benchtool/process-hash-results.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +# Turns `run-hash-benches` output into per-algorithm markdown tables. +# +# Usage: +# ./run-hash-benches > hash.txt +# ./process-hash-results.sh hash.txt > hash-table.md +# +# Parses the generic harness output; parameterized rows carry the size as a +# fourth path segment: +# runtime///: ... : 1.07us +# +# One guest run() performs max(1, 65536 / size) chained hashes (see +# guest-programs/hash-bench-common.rs) to amortize the harness's per-call +# overhead; times are divided by the same iteration count to report +# per-hash values. +# +# Roles per algorithm (naming as established in the report): +# pvm = with the polkavm64_compiler_sync_gas backend +# native = -native benchmark = host native build (-C target-cpu=native) +# portable = with the native backend = host portable build + +set -eu + +awk ' +$1 ~ /^runtime\// { + n = split($1, path, "/") + if (n < 3) next + bench = path[2] + backend = path[3]; sub(/:$/, "", backend) + size = (n >= 4) ? path[4] : "-" + sub(/:$/, "", size) + time = $NF + + ns = time + if (time ~ /ns$/) { sub(/ns$/, "", ns); ns += 0 } + else if (time ~ /us$/) { sub(/us$/, "", ns); ns *= 1000 } + else if (time ~ /ms$/) { sub(/ms$/, "", ns); ns *= 1000000 } + else if (time ~ /s$/) { sub(/s$/, "", ns); ns *= 1000000000 } + else next + + # Undo the guest-side amortization loop (see header). + bytes = (size == "-") ? 4096 : size + iterations = int(65536 / bytes) + if (iterations < 1) iterations = 1 + ns /= iterations + + algo = bench + tuned_artifact = sub(/-native$/, "", algo) + + if (!(algo in algo_seen)) { algo_seen[algo] = ++algo_count; algos[algo_count] = algo } + if (!((algo, size) in size_seen)) { + size_seen[algo, size] = 1 + sizes_for[algo] = sizes_for[algo] " " size + } + + if (tuned_artifact && backend == "native") native[algo, size] = ns + else if (backend == "native") portable[algo, size] = ns + else if (backend == "polkavm64_compiler_sync_gas") pvm[algo, size] = ns +} + +function fmt(ns) { + if (ns == "") return "-" + if (ns < 1000) return sprintf("%.0f ns", ns) + if (ns < 1000000) return sprintf("%.2f µs", ns / 1000) + return sprintf("%.2f ms", ns / 1000000) +} + +function ratio(a, b) { + if (a == "" || b == "" || b == 0) return "-" + return sprintf("%.2fx", a / b) +} + +function fmt_size(bytes) { + if (bytes == "-") return "default" + if (bytes < 1024) return bytes " B" + if (bytes < 1048576) return (bytes / 1024) " KiB" + return (bytes / 1048576) " MiB" +} + +END { + print "*pvm* = PVM guest blob (recompiler, sync gas) · *native* = host native" + print "build (`-C target-cpu=native`, build machine only) · *portable* = host" + print "portable build (runs on any x86-64; crates may runtime-dispatch, e.g." + print "sha2 uses SHA-NI where available)" + print "" + for (a = 1; a <= algo_count; a++) { + algo = algos[a] + printf "## %s\n\n", algo + print "| size | pvm | native | portable | pvm/native | pvm/portable | native/portable |" + print "|---:|---:|---:|---:|---:|---:|---:|" + n = split(sizes_for[algo], size_list, " ") + for (i = 1; i <= n; i++) { + size = size_list[i] + t_pvm = pvm[algo, size]; t_nat = native[algo, size]; t_por = portable[algo, size] + printf "| %s | %s | %s | %s | %s | %s | %s |\n", \ + fmt_size(size), fmt(t_pvm), fmt(t_nat), fmt(t_por), \ + ratio(t_pvm, t_nat), ratio(t_pvm, t_por), ratio(t_nat, t_por) + } + printf "\n" + } +} +' "$@" diff --git a/tools/benchtool/process-results.sh b/tools/benchtool/process-results.sh deleted file mode 100755 index f17a6717a..000000000 --- a/tools/benchtool/process-results.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/sh - -# Turns `benchtool bench-hash --csv` output into per-algorithm markdown tables. -# -# Usage: -# benchtool bench-hash --csv blake2_256 sha2_256 ... > results.csv -# ./process-results.sh results.csv > result-table.md -# -# Artifact roles: -# bench-hash -> pvm (the PVM blob) -# libbench_hash_native -> native (host native: -C target-cpu=native; -# "_simd" accepted for old CSVs) -# libbench_hash -> portable (host portable build) -# The PVM column gets ratios against both native and portable; a -# native/portable ratio shows what -C target-cpu=native buys (or costs) on -# the host. Any other artifact gets its own column with a ratio vs native. -# Checksums are cross-checked: rows of the same (algo, size) must agree. - -set -eu - -awk -F, ' -NR == 1 && $1 == "artifact" { next } # header -NF < 6 { next } -{ - artifact = $1; algo = $2; size = $3; ns = $4; checksum = $6 - - if (!(artifact in artifact_seen)) { - artifact_seen[artifact] = ++artifact_count - artifacts[artifact_count] = artifact - if (artifact ~ /_(native|simd)$/) { native = artifact } - else if (artifact ~ /^libbench/) { portable = artifact } - else if (pvm == "") { pvm = artifact } - } - if (!((algo, size) in row_seen)) { - row_seen[algo, size] = 1 - if (!(algo in algo_seen)) { algo_seen[algo] = ++algo_count; algos[algo_count] = algo } - sizes_for[algo] = sizes_for[algo] " " size - } - - time[artifact, algo, size] = ns - - if ((algo, size) in want_checksum) { - if (want_checksum[algo, size] != checksum) { - printf "WARNING: checksum mismatch for %s/%s: %s vs %s\n", \ - algo, size, want_checksum[algo, size], checksum > "/dev/stderr" - } - } else { - want_checksum[algo, size] = checksum - } -} - -function fmt(ns) { - if (ns == "") return "-" - if (ns < 1000) return sprintf("%.0f ns", ns) - if (ns < 1000000) return sprintf("%.2f µs", ns / 1000) - return sprintf("%.2f ms", ns / 1000000) -} - -function ratio(a, b) { - if (a == "" || b == "" || b == 0) return "-" - return sprintf("%.2fx", a / b) -} - -function fmt_size(bytes) { - if (bytes < 1024) return bytes " B" - if (bytes < 1048576) return (bytes / 1024) " KiB" - return (bytes / 1048576) " MiB" -} - -END { - # Other artifacts = everything that is not one of the three roles. - other_count = 0 - for (i = 1; i <= artifact_count; i++) { - if (artifacts[i] != pvm && artifacts[i] != native && artifacts[i] != portable) { - others[++other_count] = artifacts[i] - } - } - - print "*pvm* = PVM guest blob (recompiler, sync gas) · *native* = host native" - print "build (`-C target-cpu=native`, build machine only) · *portable* = host" - print "portable build (runs on any x86-64; crates may runtime-dispatch, e.g." - print "sha2 uses SHA-NI where available)" - print "" - - for (a = 1; a <= algo_count; a++) { - algo = algos[a] - printf "## %s\n\n", algo - - printf "| size |" - if (pvm != "") printf " pvm |" - if (native != "") printf " native |" - if (portable != "") printf " portable |" - if (pvm != "" && native != "") printf " pvm/native |" - if (pvm != "" && portable != "") printf " pvm/portable |" - if (native != "" && portable != "") printf " native/portable |" - for (i = 1; i <= other_count; i++) printf " %s | %s/native |", others[i], others[i] - printf "\n|---:|" - cols = (pvm != "") + (native != "") + (portable != "") \ - + (pvm != "" && native != "") + (pvm != "" && portable != "") \ - + (native != "" && portable != "") + 2 * other_count - for (i = 0; i < cols; i++) printf "---:|" - printf "\n" - - n = split(sizes_for[algo], size_list, " ") - for (s = 1; s <= n; s++) { - size = size_list[s] - t_pvm = time[pvm, algo, size] - t_nat = time[native, algo, size] - t_por = time[portable, algo, size] - printf "| %s |", fmt_size(size) - if (pvm != "") printf " %s |", fmt(t_pvm) - if (native != "") printf " %s |", fmt(t_nat) - if (portable != "") printf " %s |", fmt(t_por) - if (pvm != "" && native != "") printf " %s |", ratio(t_pvm, t_nat) - if (pvm != "" && portable != "") printf " %s |", ratio(t_pvm, t_por) - if (native != "" && portable != "") printf " %s |", ratio(t_nat, t_por) - for (i = 1; i <= other_count; i++) { - t_o = time[others[i], algo, size] - printf " %s | %s |", fmt(t_o), ratio(t_o, t_nat) - } - printf "\n" - } - printf "\n" - } -} -' "$@" From 1326240f80af7eadd915c989c266a2b09ab440e0 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 11:01:44 +0200 Subject: [PATCH 22/25] tools/hash-asm-tests/Cargo.lock --- tools/hash-asm-tests/Cargo.lock | 429 ++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 tools/hash-asm-tests/Cargo.lock diff --git a/tools/hash-asm-tests/Cargo.lock b/tools/hash-asm-tests/Cargo.lock new file mode 100644 index 000000000..ddaaaa8a3 --- /dev/null +++ b/tools/hash-asm-tests/Cargo.lock @@ -0,0 +1,429 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] + +[[package]] +name = "hash-asm-tests" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "polkavm", + "polkavm-linker", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "picosimd" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7823ffd00d2b55ebe51750a19f47f2a33cb1f1d135f5cba893379b81c4d44856" + +[[package]] +name = "polkavm" +version = "0.37.0" +dependencies = [ + "libc", + "log", + "picosimd", + "polkavm-assembler", + "polkavm-common", + "polkavm-linux-raw", +] + +[[package]] +name = "polkavm-assembler" +version = "0.37.0" +dependencies = [ + "log", +] + +[[package]] +name = "polkavm-common" +version = "0.37.0" +dependencies = [ + "log", + "picosimd", + "polkavm-assembler", +] + +[[package]] +name = "polkavm-linker" +version = "0.37.0" +dependencies = [ + "dirs", + "gimli", + "hashbrown 0.14.5", + "log", + "object", + "polkavm-common", + "regalloc2", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linux-raw" +version = "0.37.0" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +dependencies = [ + "hashbrown 0.13.2", + "log", + "rustc-hash", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From 1ef7253b2246ba61e175d42dbdfd8cc6d18ec060 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:26:27 +0200 Subject: [PATCH 23/25] some local utils --- tools/benchtool/run-all | 18 ++++++++++++++++++ tools/benchtool/run-crypto-benches | 19 +++++++++++++++++++ tools/benchtool/run-hash-benches | 12 ++++++++++++ 3 files changed, 49 insertions(+) create mode 100755 tools/benchtool/run-all create mode 100755 tools/benchtool/run-crypto-benches create mode 100755 tools/benchtool/run-hash-benches diff --git a/tools/benchtool/run-all b/tools/benchtool/run-all new file mode 100755 index 000000000..2f065d8b7 --- /dev/null +++ b/tools/benchtool/run-all @@ -0,0 +1,18 @@ +#!/bin/bash + +DIR=output_00 + +mkdir -p $DIR + +run-hash-benches > $DIR/hash-00.csv +run-hash-benches > $DIR/hash-01.csv +run-hash-benches > $DIR/hash-02.csv +run-hash-benches > $DIR/hash-03.csv +run-hash-benches > $DIR/hash-05.csv + + +run-crypto-benches > $DIR/crypto-00.txt +run-crypto-benches > $DIR/crypto-01.txt +run-crypto-benches > $DIR/crypto-02.txt +run-crypto-benches > $DIR/crypto-03.txt +run-crypto-benches > $DIR/crypto-05.txt diff --git a/tools/benchtool/run-crypto-benches b/tools/benchtool/run-crypto-benches new file mode 100755 index 000000000..52af850dd --- /dev/null +++ b/tools/benchtool/run-crypto-benches @@ -0,0 +1,19 @@ +#!/bin/bash + +cargo build --release +# cargo run --release -- benchmark ed25519 +# cargo run --release -- benchmark ed25519-zebra +# cargo run --release -- benchmark sr25519 +# cargo run --release -- benchmark ecdsa-k256 +# cargo run --release -- benchmark ecdsa-libsecp +# cargo run --release -- benchmark recover-k256 +# cargo run --release -- benchmark recover-libsecp + + +target/release/benchtool benchmark ed25519 +target/release/benchtool benchmark ed25519-zebra +target/release/benchtool benchmark sr25519 +target/release/benchtool benchmark ecdsa-k256 +target/release/benchtool benchmark ecdsa-libsecp +target/release/benchtool benchmark recover-k256 +target/release/benchtool benchmark recover-libsecp diff --git a/tools/benchtool/run-hash-benches b/tools/benchtool/run-hash-benches new file mode 100755 index 000000000..907afe779 --- /dev/null +++ b/tools/benchtool/run-hash-benches @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +SIZES="${SIZES:-32,128,512,4096,65536,1048576}" + +cargo build --release + +# No trailing slash: also matches the "-native" host-library variants. +for b in blake2-128 blake2-256 keccak-256 keccak-512 sha2-256 twox-64 twox-128 twox-256; do + target/release/benchtool benchmark "runtime/$b" --size "$SIZES" +done From 5f91214d118140baf2aa2429bb7887126a48aa0a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:00:23 +0200 Subject: [PATCH 24/25] investigating crypto host --- guest-programs/build-crypto-native.sh | 41 +++++++++++++++++++++++ tools/benchtool/process-crypto-results.sh | 30 ++++++++++++----- 2 files changed, 62 insertions(+), 9 deletions(-) create mode 100755 guest-programs/build-crypto-native.sh diff --git a/guest-programs/build-crypto-native.sh b/guest-programs/build-crypto-native.sh new file mode 100755 index 000000000..3dbc64f22 --- /dev/null +++ b/guest-programs/build-crypto-native.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +# Builds each signature benchmark's host library in two variants (same scheme +# as build-hash-native.sh): +# libbench_.so - host portable: runs on any x86-64 +# libbench__native.so - host native: -C target-cpu=native, all ISA +# extensions of the BUILD machine enabled +# unconditionally +# +# Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool +# auto-discovers them as "" and "-native". +# +# Note: on stable Rust the native variant changes little for this scalar code +# (measured on Zen 3: ecdsa-k256 -7%, everything else within a few percent). +# curve25519-dalek's SIMD/IFMA backends need nightly and are NOT enabled by +# target-cpu=native here - see the report's toolchain note. +# +# WARNING: never copy *_native.so between machines - they are compiled for the +# exact CPU they were built on and will crash (SIGILL) on CPUs lacking any of +# their instruction-set extensions. Always rebuild locally. +# +# Built with the stable toolchain - see the note in build-benchmarks.sh. + +set -ex +cd "${0%/*}" + +out=target/x86_64-unknown-linux-gnu/release + +crates="bench-ed25519 bench-ed25519-zebra bench-sr25519 bench-ecdsa-k256 \ + bench-ecdsa-libsecp bench-recover-k256 bench-recover-libsecp" + +for crate in $crates; do + lib=$(echo "$crate" | tr - _) + RUSTFLAGS="-C target-cpu=native" cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + cp "$out/lib$lib.so" "$out/lib${lib}_native.so" +done + +# Rebuild without the CPU features; overwrites the portable variants. +for crate in $crates; do + cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" +done diff --git a/tools/benchtool/process-crypto-results.sh b/tools/benchtool/process-crypto-results.sh index 3310ee81f..31af97638 100755 --- a/tools/benchtool/process-crypto-results.sh +++ b/tools/benchtool/process-crypto-results.sh @@ -3,9 +3,12 @@ # Turns the generic-harness output of run-crypto-benches into a markdown # summary table: per benchmark, host vs PVM (64-bit, compiler, sync gas). # -# The "host" column is whichever build the benchmark libraries were compiled -# with (host portable by default; host native if built with -# -C target-cpu=native) - label the resulting table accordingly. +# Roles (same naming as the report / process-hash-results.sh): +# host portable = with the native backend (plain build) +# host native = -native with the native backend +# (-C target-cpu=native, built by build-crypto-native.sh; +# column shows "-" when those libraries are absent) +# pvm = with the polkavm64_compiler_sync_gas backend # # Usage: # ./run-crypto-benches > crypto.txt @@ -29,8 +32,14 @@ $1 ~ /^runtime\// { else if (time ~ /s$/) { sub(/s$/, "", ns); ns *= 1000000000 } else next + if (bench ~ /-native$/) { + base = bench; sub(/-native$/, "", base) + if (backend == "native") hostnative[base] = ns + next + } + if (!(bench in seen)) { seen[bench] = ++count; benches[count] = bench } - if (backend == "native") native[bench] = ns + if (backend == "native") portable[bench] = ns if (backend == "polkavm64_compiler_sync_gas") pvm[bench] = ns } @@ -41,13 +50,16 @@ function fmt(ns) { return sprintf("%.2f ms", ns / 1000000) } +function ratio(a, b) { + return (a != "" && b != "") ? sprintf("%.2fx", a / b) : "-" +} + END { - print "| benchmark | host | PVM (64-bit, sync gas) | ratio |" - print "|---|---:|---:|---:|" + print "| benchmark | host portable | host native | PVM (64-bit, sync gas) | pvm/portable | pvm/native |" + print "|---|---:|---:|---:|---:|---:|" for (i = 1; i <= count; i++) { b = benches[i] - ratio = (native[b] != "" && pvm[b] != "") ? sprintf("%.2fx", pvm[b] / native[b]) : "-" - printf "| %s | %s | %s | %s |\n", b, fmt(native[b]), fmt(pvm[b]), ratio + printf "| %s | %s | %s | %s | %s | %s |\n", b, fmt(portable[b]), fmt(hostnative[b]), fmt(pvm[b]), ratio(pvm[b], portable[b]), ratio(pvm[b], hostnative[b]) } } -' "$@" +' "$@" \ No newline at end of file From 3ff543925a9e95ff3b3e235acaa586c63e6d6a0c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 3 Aug 2026 14:35:02 +0200 Subject: [PATCH 25/25] recent nightly used --- guest-programs/build-benchmarks.sh | 15 +++++++++------ guest-programs/build-crypto-native.sh | 18 +++++++++++------- guest-programs/build-hash-native.sh | 9 ++++++--- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/guest-programs/build-benchmarks.sh b/guest-programs/build-benchmarks.sh index 07a655a5a..b5f5de975 100755 --- a/guest-programs/build-benchmarks.sh +++ b/guest-programs/build-benchmarks.sh @@ -105,12 +105,15 @@ function build_benchmark() { RUSTFLAGS="-C target-cpu=mvp -C target-feature=-sign-ext $extra_flags" cargo build -q --target=wasm32-unknown-unknown --release --bin $1 -p $1 fi - # Native libraries are deliberately built with the stable toolchain (the - # directory's nightly pin exists only for -Zbuild-std guest builds; the - # pinned nightly's x86-64 codegen is measurably worse, e.g. ~3x slower - # SHA-NI sha256). Exception: bench-memset's compiler_builtins dependency - # requires nightly. - native_toolchain="+1.86.0" + # Toolchain for the host library builds (the directory's nightly pin + # applies only to -Zbuild-std guest builds). Host baselines are + # toolchain-sensitive - nightly degrades sha2 (SHA-NI spills) and + # keccak-native on Zen 4 (AVX-512), but is the ONLY way to get + # curve25519-dalek's SIMD/IFMA backends - so set this deliberately and + # label results with it. Override: NATIVE_TOOLCHAIN="+1.86.0" ./build-... + # Exception: bench-memset's compiler_builtins dependency requires + # nightly (empty = the directory's pinned nightly). + native_toolchain="${NATIVE_TOOLCHAIN:-+nightly-2026-08-01}" if [ "$1" == "bench-memset" ]; then native_toolchain="" fi diff --git a/guest-programs/build-crypto-native.sh b/guest-programs/build-crypto-native.sh index 3dbc64f22..548644c67 100755 --- a/guest-programs/build-crypto-native.sh +++ b/guest-programs/build-crypto-native.sh @@ -10,20 +10,24 @@ # Both land in target/x86_64-unknown-linux-gnu/release/, where benchtool # auto-discovers them as "" and "-native". # -# Note: on stable Rust the native variant changes little for this scalar code -# (measured on Zen 3: ecdsa-k256 -7%, everything else within a few percent). -# curve25519-dalek's SIMD/IFMA backends need nightly and are NOT enabled by -# target-cpu=native here - see the report's toolchain note. +# Note: the toolchain matters more than target-cpu here. On stable, native +# changes little for this scalar code (Zen 3: ecdsa-k256 -7%, rest within a +# few percent) and curve25519-dalek's SIMD/IFMA backends are unavailable; +# on nightly those backends compile in (runtime-dispatched; ~1.6x faster +# 25519 on AVX-512-IFMA CPUs) - see the report's toolchain note. # # WARNING: never copy *_native.so between machines - they are compiled for the # exact CPU they were built on and will crash (SIGILL) on CPUs lacking any of # their instruction-set extensions. Always rebuild locally. # -# Built with the stable toolchain - see the note in build-benchmarks.sh. +# Host baselines are toolchain-sensitive - see the note in +# build-benchmarks.sh. Override: NATIVE_TOOLCHAIN="+1.86.0" ./build-crypto-native.sh set -ex cd "${0%/*}" +native_toolchain="${NATIVE_TOOLCHAIN:-+nightly-2026-08-01}" + out=target/x86_64-unknown-linux-gnu/release crates="bench-ed25519 bench-ed25519-zebra bench-sr25519 bench-ecdsa-k256 \ @@ -31,11 +35,11 @@ crates="bench-ed25519 bench-ed25519-zebra bench-sr25519 bench-ecdsa-k256 \ for crate in $crates; do lib=$(echo "$crate" | tr - _) - RUSTFLAGS="-C target-cpu=native" cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + RUSTFLAGS="-C target-cpu=native" cargo $native_toolchain build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" cp "$out/lib$lib.so" "$out/lib${lib}_native.so" done # Rebuild without the CPU features; overwrites the portable variants. for crate in $crates; do - cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + cargo $native_toolchain build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" done diff --git a/guest-programs/build-hash-native.sh b/guest-programs/build-hash-native.sh index f69024f11..4a571e0a5 100755 --- a/guest-programs/build-hash-native.sh +++ b/guest-programs/build-hash-native.sh @@ -15,22 +15,25 @@ # exact CPU they were built on and will crash (SIGILL) on CPUs lacking any of # their instruction-set extensions. Always rebuild locally. # -# Built with the stable toolchain - see the note in build-benchmarks.sh. +# Host baselines are toolchain-sensitive - see the note in +# build-benchmarks.sh. Override: NATIVE_TOOLCHAIN="+1.86.0" ./build-hash-native.sh set -ex cd "${0%/*}" +native_toolchain="${NATIVE_TOOLCHAIN:-+nightly-2026-08-01}" + out=target/x86_64-unknown-linux-gnu/release for crate in bench-blake2-128 bench-blake2-256 bench-blake2-256-asm bench-keccak-256 bench-keccak-512 \ bench-sha2-256 bench-twox-64 bench-twox-128 bench-twox-256; do lib=$(echo "$crate" | tr - _) - RUSTFLAGS="-C target-cpu=native" cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + RUSTFLAGS="-C target-cpu=native" cargo $native_toolchain build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" cp "$out/lib$lib.so" "$out/lib${lib}_native.so" done # Rebuild without the CPU features; overwrites the portable variants. for crate in bench-blake2-128 bench-blake2-256 bench-blake2-256-asm bench-keccak-256 bench-keccak-512 \ bench-sha2-256 bench-twox-64 bench-twox-128 bench-twox-256; do - cargo +1.86.0 build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" + cargo $native_toolchain build --target=x86_64-unknown-linux-gnu --release --lib -p "$crate" done