Skip to content

Commit 12909f1

Browse files
authored
Merge branch 'main' into feat/disk-spill-v2-parallel-r2
2 parents ac9cfe8 + fcff74a commit 12909f1

20 files changed

Lines changed: 2944 additions & 22 deletions

File tree

.github/scripts/publish_bench_vs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,4 @@ fi
8181

8282
curl -X POST "$WEBHOOK_URL" \
8383
-H 'Content-Type: application/json; charset=utf-8' \
84-
--data '{"blocks":[{"type":"header","text":{"type":"plain_text","text":"Lambda VM vs SP1 v6 - Nightly Benchmark"}},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"'"$RESULTS_MRKDWN"'"}}'"$PROJ_SECTION"']}'
84+
--data '{"blocks":[{"type":"header","text":{"type":"plain_text","text":"Lambda VM vs SP1 v6 - Nightly Benchmark"}},{"type":"context","elements":[{"type":"mrkdwn","text":"*Program:* Fibonacci · *Device:* CPU"}]},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"'"$RESULTS_MRKDWN"'"}}'"$PROJ_SECTION"']}'

.github/workflows/bench-vs-nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
- name: Run nightly benchmark
4444
run: |
4545
bash ./bench_vs/run.sh \
46-
--steps 1000000 2000000 4000000 8000000 \
46+
--steps 1000000 2000000 4000000 8000000 16000000 \
4747
--report-dir bench_vs_artifacts \
4848
--no-color
4949
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.attribute 5, "rv64i2p1_m2p0_zmmul1p0"
2+
.Lfunc_end0:
3+
.globl main
4+
main:
5+
# Allocate 200 bytes on the stack for the Keccak state (25 × u64)
6+
addi sp, sp, -200
7+
8+
# Zero out the state (200 bytes = 25 doublewords)
9+
mv t0, sp
10+
li t1, 25
11+
.Lzero_loop:
12+
sd zero, 0(t0)
13+
addi t0, t0, 8
14+
addi t1, t1, -1
15+
bnez t1, .Lzero_loop
16+
17+
# Call keccak-f[1600] permutation
18+
# a0 = pointer to 200-byte state
19+
# a7 = syscall number (0xFFFFFFFFFFFFFFFE = u64::MAX - 1)
20+
mv a0, sp
21+
li a7, -2
22+
ecall
23+
24+
# Commit the post-permutation state so the test can verify the KAT.
25+
# Commit syscall: a0=fd(1), a1=buf_addr, a2=count, a7=64
26+
li a0, 1
27+
mv a1, sp
28+
li a2, 200
29+
li a7, 64
30+
ecall
31+
32+
# Restore stack and halt
33+
addi sp, sp, 200
34+
li a0, 0
35+
li a7, 93
36+
ecall
37+
.Lfunc_end1:
38+
.size main, .Lfunc_end1-main
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.attribute 5, "rv64i2p1_m2p0_zmmul1p0"
2+
.Lfunc_end0:
3+
.globl main
4+
main:
5+
# Allocate 200 bytes on the stack for the Keccak state (25 × u64).
6+
addi sp, sp, -200
7+
8+
# Initialize a non-zero, deterministic state: lane[i] = i + 1.
9+
# Used by the host test as the initial state for tiny-keccak::keccakf
10+
# cross-checking.
11+
mv t0, sp
12+
li t1, 1
13+
li t2, 26
14+
.Linit_loop:
15+
sd t1, 0(t0)
16+
addi t0, t0, 8
17+
addi t1, t1, 1
18+
bne t1, t2, .Linit_loop
19+
20+
# First keccak-f[1600] call.
21+
mv a0, sp
22+
li a7, -2
23+
ecall
24+
25+
# Second keccak-f[1600] call on the result.
26+
mv a0, sp
27+
li a7, -2
28+
ecall
29+
30+
# Third keccak-f[1600] call on the result.
31+
mv a0, sp
32+
li a7, -2
33+
ecall
34+
35+
# Commit the final 200-byte state.
36+
li a0, 1
37+
mv a1, sp
38+
li a2, 200
39+
li a7, 64
40+
ecall
41+
42+
# Restore stack and halt.
43+
addi sp, sp, 200
44+
li a0, 0
45+
li a7, 93
46+
ecall
47+
.Lfunc_end1:
48+
.size main, .Lfunc_end1-main

executor/src/vm/instruction/execution.rs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@ use crate::vm::{
88
const REGULAR_PC_UPDATE: u64 = 4;
99

1010
pub enum SyscallNumbers {
11+
// Placeholder discriminant. The actual syscall value is KECCAK_SYSCALL_NUMBER.
12+
KeccakPermute = 0,
1113
Print = 1,
1214
Panic = 2,
1315
Commit = 64,
1416
Halt = 93,
1517
}
1618

19+
/// Syscall number for KeccakPermute (u64::MAX - 1 = 0xFFFF_FFFF_FFFF_FFFE).
20+
///
21+
/// Cannot be an enum discriminant because it exceeds isize::MAX.
22+
pub const KECCAK_SYSCALL_NUMBER: u64 = u64::MAX - 1;
23+
const KECCAK_STATE_BYTES: u64 = 25 * 8;
24+
1725
impl TryFrom<u64> for SyscallNumbers {
1826
type Error = ();
1927
fn try_from(value: u64) -> Result<Self, Self::Error> {
@@ -22,6 +30,7 @@ impl TryFrom<u64> for SyscallNumbers {
2230
2 => Ok(SyscallNumbers::Panic),
2331
64 => Ok(SyscallNumbers::Commit),
2432
93 => Ok(SyscallNumbers::Halt),
33+
v if v == KECCAK_SYSCALL_NUMBER => Ok(SyscallNumbers::KeccakPermute),
2534
_ => Err(()),
2635
}
2736
}
@@ -324,6 +333,32 @@ impl Instruction {
324333
src2_val = buf_addr;
325334
dst_val = count;
326335
}
336+
SyscallNumbers::KeccakPermute => {
337+
// keccak-f[1600] permutation on 200 bytes (25 × u64) at address in x10
338+
let state_addr = registers.read(10)?;
339+
if !state_addr.is_multiple_of(8) {
340+
return Err(ExecutionError::UnalignedKeccakStateAddress(state_addr));
341+
}
342+
state_addr
343+
.checked_add(KECCAK_STATE_BYTES - 1)
344+
.ok_or(ExecutionError::KeccakStateAddressOverflow(state_addr))?;
345+
346+
let mut state = [0u64; 25];
347+
for (i, lane) in state.iter_mut().enumerate() {
348+
let lane_addr = state_addr
349+
.checked_add((i as u64) * 8)
350+
.ok_or(ExecutionError::KeccakStateAddressOverflow(state_addr))?;
351+
*lane = memory.load_doubleword(lane_addr)?;
352+
}
353+
keccak_f1600(&mut state);
354+
for (i, &lane) in state.iter().enumerate() {
355+
let lane_addr = state_addr
356+
.checked_add((i as u64) * 8)
357+
.ok_or(ExecutionError::KeccakStateAddressOverflow(state_addr))?;
358+
memory.store_doubleword(lane_addr, lane)?;
359+
}
360+
src2_val = state_addr;
361+
}
327362
SyscallNumbers::Halt => {
328363
// halt
329364
return Ok(Log {
@@ -496,4 +531,177 @@ pub enum ExecutionError {
496531
InvalidWSuffixOperation(ArithOp),
497532
#[error("Invalid commit fd: expected 1 (stdout), got {0}")]
498533
InvalidCommitFd(u64),
534+
#[error("Unaligned Keccak state address: {0:#018x}")]
535+
UnalignedKeccakStateAddress(u64),
536+
#[error("Keccak state address range overflows: {0:#018x}")]
537+
KeccakStateAddressOverflow(u64),
538+
}
539+
540+
// =============================================================================
541+
// Keccak-f[1600] permutation
542+
// =============================================================================
543+
544+
/// Round constants for Keccak-f[1600] (24 rounds).
545+
pub const KECCAK_RC: [u64; 24] = [
546+
0x0000000000000001,
547+
0x0000000000008082,
548+
0x800000000000808A,
549+
0x8000000080008000,
550+
0x000000000000808B,
551+
0x0000000080000001,
552+
0x8000000080008081,
553+
0x8000000000008009,
554+
0x000000000000008A,
555+
0x0000000000000088,
556+
0x0000000080008009,
557+
0x000000008000000A,
558+
0x000000008000808B,
559+
0x800000000000008B,
560+
0x8000000000008089,
561+
0x8000000000008003,
562+
0x8000000000008002,
563+
0x8000000000000080,
564+
0x000000000000800A,
565+
0x800000008000000A,
566+
0x8000000080008081,
567+
0x8000000000008080,
568+
0x0000000080000001,
569+
0x8000000080008008,
570+
];
571+
572+
/// Rotation offsets R[x][y] for the rho step of Keccak-f[1600].
573+
pub const KECCAK_RHO: [[u32; 5]; 5] = [
574+
[0, 36, 3, 41, 18],
575+
[1, 44, 10, 45, 2],
576+
[62, 6, 43, 15, 61],
577+
[28, 55, 25, 21, 56],
578+
[27, 20, 39, 8, 14],
579+
];
580+
581+
/// Apply the Keccak-f[1600] permutation (24 rounds) to a 25-word state.
582+
///
583+
/// The state is indexed as `state[x + 5*y]` where `x, y ∈ {0..4}`.
584+
pub fn keccak_f1600(state: &mut [u64; 25]) {
585+
for &rc in &KECCAK_RC {
586+
// θ (theta)
587+
let mut c = [0u64; 5];
588+
for x in 0..5 {
589+
c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20];
590+
}
591+
let mut d = [0u64; 5];
592+
for x in 0..5 {
593+
d[x] = c[(x + 4) % 5] ^ c[(x + 1) % 5].rotate_left(1);
594+
}
595+
for x in 0..5 {
596+
for y in 0..5 {
597+
state[x + 5 * y] ^= d[x];
598+
}
599+
}
600+
601+
// ρ (rho) and π (pi)
602+
let mut b = [0u64; 25];
603+
for x in 0..5 {
604+
for y in 0..5 {
605+
b[y + 5 * ((2 * x + 3 * y) % 5)] = state[x + 5 * y].rotate_left(KECCAK_RHO[x][y]);
606+
}
607+
}
608+
609+
// χ (chi)
610+
for x in 0..5 {
611+
for y in 0..5 {
612+
state[x + 5 * y] =
613+
b[x + 5 * y] ^ (!b[(x + 1) % 5 + 5 * y] & b[(x + 2) % 5 + 5 * y]);
614+
}
615+
}
616+
617+
// ι (iota)
618+
state[0] ^= rc;
619+
}
620+
}
621+
622+
#[cfg(test)]
623+
mod tests {
624+
use super::*;
625+
626+
#[test]
627+
fn test_keccak_f1600_zero_input() {
628+
let mut state = [0u64; 25];
629+
keccak_f1600(&mut state);
630+
631+
let expected: [u64; 25] = [
632+
0xF1258F7940E1DDE7,
633+
0x84D5CCF933C0478A,
634+
0xD598261EA65AA9EE,
635+
0xBD1547306F80494D,
636+
0x8B284E056253D057,
637+
0xFF97A42D7F8E6FD4,
638+
0x90FEE5A0A44647C4,
639+
0x8C5BDA0CD6192E76,
640+
0xAD30A6F71B19059C,
641+
0x30935AB7D08FFC64,
642+
0xEB5AA93F2317D635,
643+
0xA9A6E6260D712103,
644+
0x81A57C16DBCF555F,
645+
0x43B831CD0347C826,
646+
0x01F22F1A11A5569F,
647+
0x05E5635A21D9AE61,
648+
0x64BEFEF28CC970F2,
649+
0x613670957BC46611,
650+
0xB87C5A554FD00ECB,
651+
0x8C3EE88A1CCF32C8,
652+
0x940C7922AE3A2614,
653+
0x1841F924A2C509E4,
654+
0x16F53526E70465C2,
655+
0x75F644E97F30A13B,
656+
0xEAF1FF7B5CECA249,
657+
];
658+
659+
assert_eq!(state, expected, "keccak-f[1600] on zero input mismatch");
660+
}
661+
662+
#[test]
663+
fn test_keccak_f1600_nonzero_input() {
664+
let mut state = [0u64; 25];
665+
state[0] = 1;
666+
let original = state;
667+
keccak_f1600(&mut state);
668+
assert_ne!(state, original);
669+
assert!(state.iter().any(|&x| x != 0));
670+
}
671+
672+
#[test]
673+
fn test_keccak_syscall_rejects_unaligned_state_addr() {
674+
let mut pc = 0;
675+
let mut registers = Registers::default();
676+
let mut memory = Memory::default();
677+
678+
registers.write(17, KECCAK_SYSCALL_NUMBER).unwrap();
679+
registers.write(10, 0x1001).unwrap();
680+
681+
let err = Instruction::EcallEbreak
682+
.run(&mut pc, &mut registers, &mut memory)
683+
.unwrap_err();
684+
assert!(matches!(
685+
err,
686+
ExecutionError::UnalignedKeccakStateAddress(0x1001)
687+
));
688+
}
689+
690+
#[test]
691+
fn test_keccak_syscall_rejects_overflowing_state_range() {
692+
let mut pc = 0;
693+
let mut registers = Registers::default();
694+
let mut memory = Memory::default();
695+
696+
registers.write(17, KECCAK_SYSCALL_NUMBER).unwrap();
697+
registers.write(10, u64::MAX - 191).unwrap();
698+
699+
let err = Instruction::EcallEbreak
700+
.run(&mut pc, &mut registers, &mut memory)
701+
.unwrap_err();
702+
assert!(matches!(
703+
err,
704+
ExecutionError::KeccakStateAddressOverflow(addr) if addr == u64::MAX - 191
705+
));
706+
}
499707
}

executor/tests/asm.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,3 +801,49 @@ fn test_sub_64bit() {
801801
fn test_sub_underflow() {
802802
run_program("./program_artifacts/asm/sub_underflow.elf");
803803
}
804+
805+
// ==================== Keccak Precompile ====================
806+
807+
#[test]
808+
fn test_keccak() {
809+
// Runs keccak-f[1600] on a zeroed state and commits the 200-byte result.
810+
// Expected output is the FIPS-202 zero-input KAT.
811+
let elf_data = std::fs::read("./program_artifacts/asm/test_keccak.elf").unwrap();
812+
let program = Elf::load(&elf_data).unwrap();
813+
let executor = Executor::new(&program, vec![]).expect("Failed to create executor");
814+
let result = executor.run().expect("Failed to run program");
815+
816+
let expected_state: [u64; 25] = [
817+
0xF1258F7940E1DDE7,
818+
0x84D5CCF933C0478A,
819+
0xD598261EA65AA9EE,
820+
0xBD1547306F80494D,
821+
0x8B284E056253D057,
822+
0xFF97A42D7F8E6FD4,
823+
0x90FEE5A0A44647C4,
824+
0x8C5BDA0CD6192E76,
825+
0xAD30A6F71B19059C,
826+
0x30935AB7D08FFC64,
827+
0xEB5AA93F2317D635,
828+
0xA9A6E6260D712103,
829+
0x81A57C16DBCF555F,
830+
0x43B831CD0347C826,
831+
0x01F22F1A11A5569F,
832+
0x05E5635A21D9AE61,
833+
0x64BEFEF28CC970F2,
834+
0x613670957BC46611,
835+
0xB87C5A554FD00ECB,
836+
0x8C3EE88A1CCF32C8,
837+
0x940C7922AE3A2614,
838+
0x1841F924A2C509E4,
839+
0x16F53526E70465C2,
840+
0x75F644E97F30A13B,
841+
0xEAF1FF7B5CECA249,
842+
];
843+
let mut expected_bytes = Vec::with_capacity(200);
844+
for lane in expected_state {
845+
expected_bytes.extend_from_slice(&lane.to_le_bytes());
846+
}
847+
assert_eq!(result.return_values.memory_values, expected_bytes);
848+
assert_eq!(result.return_values.register_values.0, 0);
849+
}

0 commit comments

Comments
 (0)